From b759479c75ae1441eb42b7d1d135eea0c4898b6c Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Tue, 12 Apr 2022 00:53:37 +0800 Subject: [PATCH 001/789] Chinese - Chapter 1 finished --- chapters/zh/_toctree.yml | 173 ++++++++++++++++++++++ chapters/zh/chapter1/1.mdx | 52 +++++++ chapters/zh/chapter1/10.mdx | 253 ++++++++++++++++++++++++++++++++ chapters/zh/chapter1/2.mdx | 20 +++ chapters/zh/chapter1/3.mdx | 280 ++++++++++++++++++++++++++++++++++++ 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 ++ 11 files changed, 1042 insertions(+) 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/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml new file mode 100644 index 000000000..298de22ab --- /dev/null +++ b/chapters/zh/_toctree.yml @@ -0,0 +1,173 @@ +- title: 0. 准备 + sections: + - local: chapter0/1 + title: 课程简介 + +- 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 + +- title: 2. Using 🤗 Transformers + sections: + - local: chapter2/1 + title: Introduction + - local: chapter2/2 + title: Behind the pipeline + - local: chapter2/3 + title: Models + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: Handling multiple sequences + - local: chapter2/6 + title: Putting it all together + - local: chapter2/7 + title: Basic usage completed! + - local: chapter2/8 + title: End-of-chapter quiz + quiz: 2 + +- title: 3. Fine-tuning a pretrained model + sections: + - local: chapter3/1 + title: Introduction + - local: chapter3/2 + title: Processing the data + - local: chapter3/3 + title: Fine-tuning a model with the Trainer API or Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: A full training + - local: chapter3/5 + title: Fine-tuning, Check! + - local: chapter3/6 + title: End-of-chapter quiz + quiz: 3 + +- title: 4. Sharing models and tokenizers + sections: + - local: chapter4/1 + title: The Hugging Face Hub + - local: chapter4/2 + title: Using pretrained models + - local: chapter4/3 + title: Sharing pretrained models + - local: chapter4/4 + title: Building a model card + - local: chapter4/5 + title: Part 1 completed! + - local: chapter4/6 + title: End-of-chapter quiz + quiz: 4 + +- title: 5. The 🤗 Datasets library + sections: + - local: chapter5/1 + title: Introduction + - local: chapter5/2 + title: What if my dataset isn't on the Hub? + - local: chapter5/3 + title: Time to slice and dice + - local: chapter5/4 + title: Big data? 🤗 Datasets to the rescue! + - local: chapter5/5 + title: Creating your own dataset + - local: chapter5/6 + title: Semantic search with FAISS + - local: chapter5/7 + title: 🤗 Datasets, check! + - local: chapter5/8 + title: End-of-chapter quiz + quiz: 5 + +- title: 6. The 🤗 Tokenizers library + sections: + - local: chapter6/1 + title: Introduction + - local: chapter6/2 + title: Training a new tokenizer from an old one + - local: chapter6/3 + title: Fast tokenizers' special powers + - local: chapter6/3b + title: Fast tokenizers in the QA pipeline + - local: chapter6/4 + title: Normalization and 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: Building a tokenizer, block by block + - local: chapter6/9 + title: Tokenizers, check! + - local: chapter6/10 + title: End-of-chapter quiz + quiz: 6 + +- title: 7. Main NLP tasks + sections: + - local: chapter7/1 + title: Introduction + - local: chapter7/2 + title: Token classification + - local: chapter7/3 + title: Fine-tuning a masked language model + - local: chapter7/4 + title: Translation + - local: chapter7/5 + title: Summarization + - local: chapter7/6 + title: Training a causal language model from scratch + - local: chapter7/7 + title: Question answering + - local: chapter7/8 + title: Mastering NLP + - local: chapter7/9 + title: End-of-chapter quiz + quiz: 7 + +- title: 8. How to ask for help + sections: + - local: chapter8/1 + title: Introduction + - local: chapter8/2 + title: What to do when you get an error + - local: chapter8/3 + title: Asking for help on the forums + - local: chapter8/4 + title: Debugging the training pipeline + local_fw: { pt: chapter8/4, tf: chapter8/4_tf } + - local: chapter8/5 + title: How to write a good issue + - local: chapter8/6 + title: Part 2 completed! + - local: chapter8/7 + title: End-of-chapter quiz + quiz: 8 + +- title: Hugging Face Course Event + sections: + - local: event/1 + title: Part 2 Release Event 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..1f067ab6d --- /dev/null +++ b/chapters/zh/chapter1/3.mdx @@ -0,0 +1,280 @@ +# Transformers能做什么? + + + +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 + +## 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! + +```python +⚠️ 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 +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + + +我们也可以多传几句! +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` +```python +[{'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 +{'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 +[{'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 +[{'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 +[{'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 +[{'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 +{'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 +[{'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 +[{'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 8ec6fd3680456717dab3a75115c49507c89adfd1 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Tue, 12 Apr 2022 21:26:13 +0800 Subject: [PATCH 002/789] Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index a03061dc8..8e0f0b7e9 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 ko pt ru th tr + languages: ar bn en es fa fr he 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 84af57f75..ff5039db9 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 ko pt ru th tr + languages: ar bn en es fa fr he ko pt ru th tr zh hub_base_path: https://moon-ci-docs.huggingface.co/course From b931926f84a95f24cc21d81efec548f9d5e7957d Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Tue, 12 Apr 2022 21:32:03 +0800 Subject: [PATCH 003/789] 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 --- chapters/zh/_toctree.yml | 152 +-------------------------------------- 1 file changed, 1 insertion(+), 151 deletions(-) diff --git a/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml index 298de22ab..453fc5e99 100644 --- a/chapters/zh/_toctree.yml +++ b/chapters/zh/_toctree.yml @@ -1,8 +1,3 @@ -- title: 0. 准备 - sections: - - local: chapter0/1 - title: 课程简介 - - title: 1. Transformer 模型 sections: - local: chapter1/1 @@ -25,149 +20,4 @@ title: 总结 - local: chapter1/10 title: 章末小测验 - quiz: 1 - -- title: 2. Using 🤗 Transformers - sections: - - local: chapter2/1 - title: Introduction - - local: chapter2/2 - title: Behind the pipeline - - local: chapter2/3 - title: Models - - local: chapter2/4 - title: Tokenizers - - local: chapter2/5 - title: Handling multiple sequences - - local: chapter2/6 - title: Putting it all together - - local: chapter2/7 - title: Basic usage completed! - - local: chapter2/8 - title: End-of-chapter quiz - quiz: 2 - -- title: 3. Fine-tuning a pretrained model - sections: - - local: chapter3/1 - title: Introduction - - local: chapter3/2 - title: Processing the data - - local: chapter3/3 - title: Fine-tuning a model with the Trainer API or Keras - local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - - local: chapter3/4 - title: A full training - - local: chapter3/5 - title: Fine-tuning, Check! - - local: chapter3/6 - title: End-of-chapter quiz - quiz: 3 - -- title: 4. Sharing models and tokenizers - sections: - - local: chapter4/1 - title: The Hugging Face Hub - - local: chapter4/2 - title: Using pretrained models - - local: chapter4/3 - title: Sharing pretrained models - - local: chapter4/4 - title: Building a model card - - local: chapter4/5 - title: Part 1 completed! - - local: chapter4/6 - title: End-of-chapter quiz - quiz: 4 - -- title: 5. The 🤗 Datasets library - sections: - - local: chapter5/1 - title: Introduction - - local: chapter5/2 - title: What if my dataset isn't on the Hub? - - local: chapter5/3 - title: Time to slice and dice - - local: chapter5/4 - title: Big data? 🤗 Datasets to the rescue! - - local: chapter5/5 - title: Creating your own dataset - - local: chapter5/6 - title: Semantic search with FAISS - - local: chapter5/7 - title: 🤗 Datasets, check! - - local: chapter5/8 - title: End-of-chapter quiz - quiz: 5 - -- title: 6. The 🤗 Tokenizers library - sections: - - local: chapter6/1 - title: Introduction - - local: chapter6/2 - title: Training a new tokenizer from an old one - - local: chapter6/3 - title: Fast tokenizers' special powers - - local: chapter6/3b - title: Fast tokenizers in the QA pipeline - - local: chapter6/4 - title: Normalization and 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: Building a tokenizer, block by block - - local: chapter6/9 - title: Tokenizers, check! - - local: chapter6/10 - title: End-of-chapter quiz - quiz: 6 - -- title: 7. Main NLP tasks - sections: - - local: chapter7/1 - title: Introduction - - local: chapter7/2 - title: Token classification - - local: chapter7/3 - title: Fine-tuning a masked language model - - local: chapter7/4 - title: Translation - - local: chapter7/5 - title: Summarization - - local: chapter7/6 - title: Training a causal language model from scratch - - local: chapter7/7 - title: Question answering - - local: chapter7/8 - title: Mastering NLP - - local: chapter7/9 - title: End-of-chapter quiz - quiz: 7 - -- title: 8. How to ask for help - sections: - - local: chapter8/1 - title: Introduction - - local: chapter8/2 - title: What to do when you get an error - - local: chapter8/3 - title: Asking for help on the forums - - local: chapter8/4 - title: Debugging the training pipeline - local_fw: { pt: chapter8/4, tf: chapter8/4_tf } - - local: chapter8/5 - title: How to write a good issue - - local: chapter8/6 - title: Part 2 completed! - - local: chapter8/7 - title: End-of-chapter quiz - quiz: 8 - -- title: Hugging Face Course Event - sections: - - local: event/1 - title: Part 2 Release Event + quiz: 1 \ No newline at end of file From 0778f36a829715813a7ce45f552cc5ab844715c3 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Wed, 13 Apr 2022 01:17:40 +0800 Subject: [PATCH 004/789] Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 --- chapters/zh/chapter1/3.mdx | 607 ++++++++++++++++++++----------------- 1 file changed, 327 insertions(+), 280 deletions(-) diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx index 1f067ab6d..cd6aee466 100644 --- a/chapters/zh/chapter1/3.mdx +++ b/chapters/zh/chapter1/3.mdx @@ -1,280 +1,327 @@ -# Transformers能做什么? - - - -在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 - -## 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! - -```python -⚠️ 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 -[{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` - - -我们也可以多传几句! -```python -classifier( - ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] -) -``` -```python -[{'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 -{'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 -[{'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 -[{'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 -[{'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 -[{'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 -{'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 -[{'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 -[{'translation_text': 'This course is produced by Hugging Face.'}] - -``` - -与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 - -``` -✏️快来试试吧!搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 -``` - -到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file +# Transformers, what can they do? + + + +In this section, we will look at what Transformer models can do and use our first tool from the 🤗 Transformers library: the `pipeline()` function. + + +👀 See that Open in Colab button on the top right? Click on it to open a Google Colab notebook with all the code samples of this section. This button will be present in any section containing code examples. + +If you want to run the examples locally, we recommend taking a look at the setup. + + +## 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: + +Companies using Hugging Face + +The [🤗 Transformers library](https://github.com/huggingface/transformers) provides the functionality to create and use those shared models. The [Model Hub](https://huggingface.co/models) contains thousands of pretrained models that anyone can download and use. You can also upload your own models to the Hub! + + +⚠️ The Hugging Face Hub is not limited to Transformer models. Anyone can share any kind of models or datasets they want! Create a huggingface.co account to benefit from all available features! + + +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 + + + +The most basic object in the 🤗 Transformers library is the `pipeline()` function. It connects a model with its necessary preprocessing and postprocessing steps, allowing us to directly input any text and get an intelligible answer: + +```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}] +``` + +We can even pass several sentences! + +```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}] +``` + +By default, this pipeline selects a particular pretrained model that has been fine-tuned for sentiment analysis in English. The model is downloaded and cached when you create the `classifier` object. If you rerun the command, the cached model will be used instead and there is no need to download the model again. + +There are three main steps involved when you pass some text to a pipeline: + +1. The text is preprocessed into a format the model can understand. +2. The preprocessed inputs are passed to the model. +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: + +- `feature-extraction` (get the vector representation of a text) +- `fill-mask` +- `ner` (named entity recognition) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Let's have a look at a few of these! + +## 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. + +```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]} +``` + +This pipeline is called _zero-shot_ because you don't need to fine-tune the model on your data to use it. It can directly return probability scores for any list of labels you want! + + + +✏️ **Try it out!** Play around with your own sequences and labels and see how the model behaves. + + + + +## 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. + +```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'}] +``` + +You can control how many different sequences are generated with the argument `num_return_sequences` and the total length of the output text with the argument `max_length`. + + + +✏️ **Try it out!** Use the `num_return_sequences` and `max_length` arguments to generate two sentences of 15 words each. + + + + +## 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). + +Let's try the [`distilgpt2`](https://huggingface.co/distilgpt2) model! Here's how to load it in the same pipeline as before: + +```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'}] +``` + +You can refine your search for a model by clicking on the language tags, and pick a model that will generate text in another language. The Model Hub even contains checkpoints for multilingual models that support several languages. + +Once you select a model by clicking on it, you'll see that there is a widget enabling you to try it directly online. This way you can quickly test the model's capabilities before downloading it. + + + +✏️ **Try it out!** Use the filters to find a text generation model for another language. Feel free to play with the widget and use it in a pipeline! + + + +### 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 + +The next pipeline you'll try is `fill-mask`. The idea of this task is to fill in the blanks in a given text: + +```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'}] +``` + +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. + + + +✏️ **Try it out!** Search for the `bert-base-cased` model on the Hub and identify its mask word in the Inference API widget. What does this model predict for the sentence in our `pipeline` example above? + + + +## 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: + +```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} +] +``` + +Here the model correctly identified that Sylvain is a person (PER), Hugging Face an organization (ORG), and Brooklyn a location (LOC). + +We pass the option `grouped_entities=True` in the pipeline creation function to tell the pipeline to regroup together the parts of the sentence that correspond to the same entity: here the model correctly grouped "Hugging" and "Face" as a single organization, even though the name consists of multiple words. In fact, as we will see in the next chapter, the preprocessing even splits some words into smaller parts. For instance, `Sylvain` is split into four pieces: `S`, `##yl`, `##va`, and `##in`. In the post-processing step, the pipeline successfully regrouped those pieces. + + + +✏️ **Try it out!** Search the Model Hub for a model able to do part-of-speech tagging (usually abbreviated as POS) in English. What does this model predict for the sentence in the example above? + + + +## Question answering + +The `question-answering` pipeline answers questions using information from a given 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'} +``` + +Note that this pipeline works by extracting information from the provided context; it does not generate the answer. + +## 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: + +```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 .'}] +``` + +Like with text generation, you can specify a `max_length` or a `min_length` for the result. + + +## 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: + +```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.'}] +``` + +Like with text generation and summarization, you can specify a `max_length` or a `min_length` for the result. + + + +✏️ **Try it out!** Search for translation models in other languages and try to translate the previous sentence into a few different languages. + + + +The pipelines shown so far are mostly for demonstrative purposes. They were programmed for specific tasks and cannot perform variations of them. In the next chapter, you'll learn what's inside a `pipeline()` function and how to customize its behavior. From e7100c604db051cfcfcd0c27c141fffda9ad4b9e Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Wed, 13 Apr 2022 01:40:00 +0800 Subject: [PATCH 005/789] Added a small part of the missing content --- chapters/zh/chapter1/3.mdx | 188 ++++++++++++++----------------------- 1 file changed, 73 insertions(+), 115 deletions(-) diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx index cd6aee466..1e7e91108 100644 --- a/chapters/zh/chapter1/3.mdx +++ b/chapters/zh/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Transformers, what can they do? +# Transformers能做什么? -In this section, we will look at what Transformer models can do and use our first tool from the 🤗 Transformers library: the `pipeline()` function. - +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 -👀 See that Open in Colab button on the top right? Click on it to open a Google Colab notebook with all the code samples of this section. This button will be present in any section containing code examples. +👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 -If you want to run the examples locally, we recommend taking a look at the setup. +如果您想在本地运行示例,我们建议您查看准备. -## 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: +## Transformer被应用于各个方面! +Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: -Companies using Hugging Face - -The [🤗 Transformers library](https://github.com/huggingface/transformers) provides the functionality to create and use those shared models. The [Model Hub](https://huggingface.co/models) contains thousands of pretrained models that anyone can download and use. You can also upload your own models to the Hub! +![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! -⚠️ The Hugging Face Hub is not limited to Transformer models. Anyone can share any kind of models or datasets they want! Create a huggingface.co account to benefit from all available features! +⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! -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. +在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 + +## 使用pipelines -## Working with pipelines + +(这里有一个视频,但是国内可能打不开,译者注) - -The most basic object in the 🤗 Transformers library is the `pipeline()` function. It connects a model with its necessary preprocessing and postprocessing steps, allowing us to directly input any text and get an intelligible answer: +🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: ```python from transformers import pipeline @@ -41,50 +40,45 @@ 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}] ``` -We can even pass several sentences! +我们也可以多传几句! ```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选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 -By default, this pipeline selects a particular pretrained model that has been fine-tuned for sentiment analysis in English. The model is downloaded and cached when you create the `classifier` object. If you rerun the command, the cached model will be used instead and there is no need to download the model again. +将一些文本传递到pipeline时涉及三个主要步骤: -There are three main steps involved when you pass some text to a pipeline: +1. 文本被预处理为模型可以理解的格式。 +2. 预处理的输入被传递给模型。 +3. 模型处理后输出最终人类可以理解的结果。 -1. The text is preprocessed into a format the model can understand. -2. The preprocessed inputs are passed to the model. -3. The predictions of the model are post-processed, so you can make sense of them. +目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: +* **特征提取**(获取文本的向量表示) +* **填充空缺** +* **ner**(命名实体识别) +* **问答** +* **情感分析** +* **文本摘要** +* **文本生成** +* **翻译** +* **零样本分类** -Some of the currently [available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html) are: +让我们来看看其中的一些吧! -- `feature-extraction` (get the vector representation of a text) -- `fill-mask` -- `ner` (named entity recognition) -- `question-answering` -- `sentiment-analysis` -- `summarization` -- `text-generation` -- `translation` -- `zero-shot-classification` - -Let's have a look at a few of these! - -## 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. +## 零样本分类 +我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 ```python from transformers import pipeline @@ -95,25 +89,19 @@ classifier( 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]} ``` -This pipeline is called _zero-shot_ because you don't need to fine-tune the model on your data to use it. It can directly return probability scores for any list of labels you want! - +此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! - -✏️ **Try it out!** Play around with your own sequences and labels and see how the model behaves. - +✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 - -## 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. +## 文本生成 +现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 ```python from transformers import pipeline @@ -121,7 +109,6 @@ 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 ' @@ -129,21 +116,16 @@ generator("In this course, we will teach you how to") 'data flows — data flows of various types, as seen by the ' 'HTTP'}] ``` - -You can control how many different sequences are generated with the argument `num_return_sequences` and the total length of the output text with the argument `max_length`. +您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 - -✏️ **Try it out!** Use the `num_return_sequences` and `max_length` arguments to generate two sentences of 15 words each. - +✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 +## 在pipeline中使用 Hub 中的其他模型 +前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 -## 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). - -Let's try the [`distilgpt2`](https://huggingface.co/distilgpt2) model! Here's how to load it in the same pipeline as before: +让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: ```python from transformers import pipeline @@ -153,7 +135,6 @@ 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.'}, @@ -161,34 +142,26 @@ generator( 'practice realtime, and with a hands on experience on both real ' 'time and real'}] ``` +您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 -You can refine your search for a model by clicking on the language tags, and pick a model that will generate text in another language. The Model Hub even contains checkpoints for multilingual models that support several languages. - -Once you select a model by clicking on it, you'll see that there is a widget enabling you to try it directly online. This way you can quickly test the model's capabilities before downloading it. - +通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 - -✏️ **Try it out!** Use the filters to find a text generation model for another language. Feel free to play with the widget and use it in a pipeline! - +✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! -### The Inference API +## 推理 API +所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 -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. +小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 ## 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: - +您将尝试的下一个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, @@ -199,47 +172,36 @@ unmasker("This course will teach you all about models.", top_k=2) 'token': 38163, 'token_str': ' computational'}] ``` - -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** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 - -✏️ **Try it out!** Search for the `bert-base-cased` model on the Hub and identify its mask word in the Inference API widget. What does this model predict for the sentence in our `pipeline` example above? - +✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? -## 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: - +## 命名实体识别 +命名实体识别 (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)。 -Here the model correctly identified that Sylvain is a person (PER), Hugging Face an organization (ORG), and Brooklyn a location (LOC). - -We pass the option `grouped_entities=True` in the pipeline creation function to tell the pipeline to regroup together the parts of the sentence that correspond to the same entity: here the model correctly grouped "Hugging" and "Face" as a single organization, even though the name consists of multiple words. In fact, as we will see in the next chapter, the preprocessing even splits some words into smaller parts. For instance, `Sylvain` is split into four pieces: `S`, `##yl`, `##va`, and `##in`. In the post-processing step, the pipeline successfully regrouped those pieces. +我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 - -✏️ **Try it out!** Search the Model Hub for a model able to do part-of-speech tagging (usually abbreviated as POS) in English. What does this model predict for the sentence in the example above? - +✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? -## Question answering - -The `question-answering` pipeline answers questions using information from a given context: - +## 问答系统 +问答pipeline使用来自给定上下文的信息回答问题: ```python from transformers import pipeline @@ -249,16 +211,16 @@ question_answerer( 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 that this pipeline works by extracting information from the provided context; it does not generate the answer. +klyn", +) -## Summarization +``` +请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 -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: +## 文本摘要 +文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: ```python from transformers import pipeline @@ -287,7 +249,6 @@ summarizer( """ ) ``` - ```python out [{'summary_text': ' America has changed dramatically during recent years . The ' 'number of engineering graduates in the U.S. has declined in ' @@ -297,13 +258,10 @@ summarizer( 'industrial countries in Europe and Asia, continue to encourage ' 'and advance engineering .'}] ``` +与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 -Like with text generation, you can specify a `max_length` or a `min_length` for the result. - - -## 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: +## 翻译 +对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: ```python from transformers import pipeline @@ -311,17 +269,17 @@ 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.'}] + ``` -Like with text generation and summarization, you can specify a `max_length` or a `min_length` for the result. +与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 -✏️ **Try it out!** Search for translation models in other languages and try to translate the previous sentence into a few different languages. +✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 -The pipelines shown so far are mostly for demonstrative purposes. They were programmed for specific tasks and cannot perform variations of them. In the next chapter, you'll learn what's inside a `pipeline()` function and how to customize its behavior. +到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file From 0990c773d3e0f5f1ff434a5af1d06078072d4ff9 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Wed, 13 Apr 2022 11:14:29 +0200 Subject: [PATCH 006/789] Fix style --- chapters/zh/chapter1/3.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx index 1e7e91108..076263ba4 100644 --- a/chapters/zh/chapter1/3.mdx +++ b/chapters/zh/chapter1/3.mdx @@ -132,7 +132,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` ```python out From 52b21586130d13e8cff7b7b47878877807ac265f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 10:04:37 +0200 Subject: [PATCH 007/789] chapter3 - added introduction --- chapters/fa/chapter3/1.mdx | 1 + 1 file changed, 1 insertion(+) create mode 100644 chapters/fa/chapter3/1.mdx diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/chapters/fa/chapter3/1.mdx @@ -0,0 +1 @@ + From 33915ea32f7af0443eea873f3a0f92f68a045c89 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 10:22:54 +0200 Subject: [PATCH 008/789] ch3 - intro - title --- chapters/fa/chapter3/1.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 8b1378917..0cef3bdd6 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1 +1 @@ - +### مقدمه From 6bd93263e69a2442a2fc00519a0e9c63c984f785 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 10:30:21 +0200 Subject: [PATCH 009/789] intro some text added --- chapters/fa/chapter3/1.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 0cef3bdd6..a2f9138f5 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1 +1,3 @@ -### مقدمه +# مقدمه + +در فصل ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. From 7eae7bbecd0b3dc2424b0e64d1758b3c0d18311a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 21:53:01 +0200 Subject: [PATCH 010/789] url added --- chapters/fa/chapter3/1.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index a2f9138f5..fc77c3c56 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,3 +1,6 @@ # مقدمه -در فصل ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. +در (/course/chapter2)[فصل ۲] ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ + +* چگونه یک داده بزرگ را از هاب تهیه کنید +* From cb090d1bf229796fc91786b6a0a4fa4447e22fe9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 21:55:06 +0200 Subject: [PATCH 011/789] url added --- chapters/fa/chapter3/1.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index fc77c3c56..869494168 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -4,3 +4,5 @@ * چگونه یک داده بزرگ را از هاب تهیه کنید * + +[url](www.google.com) \ No newline at end of file From b7aee3a6ec8fc72ff03ca59a700fee35047986d8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 21:55:44 +0200 Subject: [PATCH 012/789] url added --- chapters/fa/chapter3/1.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 869494168..30f4a99cd 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -2,7 +2,7 @@ در (/course/chapter2)[فصل ۲] ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ -* چگونه یک داده بزرگ را از هاب تهیه کنید +* چگونه [url](www.google.com) یک داده بزرگ را از هاب تهیه کنید * [url](www.google.com) \ No newline at end of file From 8e1e264c7c9ea71ba780d9e6ee1cf8fe866b3555 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 21:56:23 +0200 Subject: [PATCH 013/789] url added --- chapters/fa/chapter3/1.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 30f4a99cd..41dd60266 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -2,7 +2,9 @@ در (/course/chapter2)[فصل ۲] ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ -* چگونه [url](www.google.com) یک داده بزرگ را از هاب تهیه کنید +* چگونه [لینک](www.google.com) یک داده بزرگ را از هاب تهیه کنید * -[url](www.google.com) \ No newline at end of file +[url](www.google.com) + + From 0c472b689ed91f029c9199431f9ca72fe06b81fc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 21:57:39 +0200 Subject: [PATCH 014/789] url added --- chapters/fa/chapter3/1.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 41dd60266..9f9d52fbb 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,6 +1,6 @@ # مقدمه -در (/course/chapter2)[فصل ۲] ۲ نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ * چگونه [لینک](www.google.com) یک داده بزرگ را از هاب تهیه کنید * @@ -8,3 +8,4 @@ [url](www.google.com) +/course/chapter2 \ No newline at end of file From ccac309a6dfa8c02bf686fca254711722de65ebc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 22:05:43 +0200 Subject: [PATCH 015/789] bullets added --- chapters/fa/chapter3/1.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 9f9d52fbb..e37bdd7ac 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -2,10 +2,12 @@ در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ -* چگونه [لینک](www.google.com) یک داده بزرگ را از هاب تهیه کنید -* +{#if fw === 'pt'} -[url](www.google.com) +* چگونه یک داده بزرگ را از هاب تهیه کنید +* چگونه از ای پی ای آموزش دهنده سطح بالا برای فاین تیون کردن مدل استفاده کنید +* چطور یک لوپ آموزش دهنده شخصی درست کنیم +* چگونه از کتابخانه شتابدهنده استفاده کنیم برای اینکه لوپ آموزش دهنده شخصی را در هر گونه تنظیمات پراکنده اجرا کنیم +{:else} -/course/chapter2 \ No newline at end of file From 9f01f8263cf3a3ea1d0ae24ad71bacad5e0ee0b9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 19 Apr 2022 22:17:44 +0200 Subject: [PATCH 016/789] bullets added --- chapters/fa/chapter3/1.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index e37bdd7ac..18e7580dd 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -7,7 +7,7 @@ * چگونه یک داده بزرگ را از هاب تهیه کنید * چگونه از ای پی ای آموزش دهنده سطح بالا برای فاین تیون کردن مدل استفاده کنید * چطور یک لوپ آموزش دهنده شخصی درست کنیم -* چگونه از کتابخانه شتابدهنده استفاده کنیم برای اینکه لوپ آموزش دهنده شخصی را در هر گونه تنظیمات پراکنده اجرا کنیم +* چگونه از کتابخانه شتابدهنده 🤗 استفاده کنیم برای اینکه لوپ آموزش دهنده شخصی را در هر گونه تنظیمات پراکنده اجرا کنیم {:else} From 8011d3728b5ae2e517d83e3e2667696cda2126fd Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 08:52:47 +0200 Subject: [PATCH 017/789] introduction - first draft --- chapters/fa/chapter3/1.mdx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 18e7580dd..fca5f8e49 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,13 +1,21 @@ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزر ها و مدلهای از پیش آموزش داده شده را جهت انجام پیش بینی جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش آموزش داده شده را خودتان فاین تیون کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدلهای پیش تعلیم را جهت انجام پیش بینی های جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش تعلیم را خودتان بازتنظیم کنید؟ {#if fw === 'pt'} * چگونه یک داده بزرگ را از هاب تهیه کنید -* چگونه از ای پی ای آموزش دهنده سطح بالا برای فاین تیون کردن مدل استفاده کنید -* چطور یک لوپ آموزش دهنده شخصی درست کنیم -* چگونه از کتابخانه شتابدهنده 🤗 استفاده کنیم برای اینکه لوپ آموزش دهنده شخصی را در هر گونه تنظیمات پراکنده اجرا کنیم +* چگونه از API سطح بالای "Trainer" آموزش دهنده برای بازتنظیم مدل استفاده کنید +* چگونه یک چرخه تعلیم دلخواه درست کنید +* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه تعلیم دلخواه را در هر تنظیمات غیر متمرکزی استفاده کنید {:else} +* چگونه یک داده بزرگ را از هاب تهیه کنید +* چگونه از کراس برای بازتنظیم مدل استفاده کنید +* چگونه از کراس برای استخراج پیش بینی ها استفاده کنید +* چگونه از یک متریک دلخواه استفاده کنید + +{/if} + +جهت بارگذاری چک پوینت تعلیم دیده خود در هاب هاگینک فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) \ No newline at end of file From 96a3dc08f3ca648e6647bc923c3e62c964470593 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 08:54:10 +0200 Subject: [PATCH 018/789] fixed minor typo --- chapters/fa/chapter3/1.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index fca5f8e49..8ef01b751 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,6 +1,6 @@ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدلهای پیش تعلیم را جهت انجام پیش بینی های جدید بررسی کردیم. اما چگونه میتوانید یک مدل از پیش تعلیم را خودتان بازتنظیم کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدلهای پیش تعلیم را جهت انجام پیش بینی های جدید بررسی کردیم. اما چگونه میتوانید یک مدل پیش تعلیم را خودتان بازتنظیم کنید؟ {#if fw === 'pt'} From 7321d7202add8061d348be9014ea69a1969bf12f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 08:55:55 +0200 Subject: [PATCH 019/789] fixed minor typo --- chapters/fa/chapter3/1.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 8ef01b751..ccb01c2e9 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -5,9 +5,9 @@ {#if fw === 'pt'} * چگونه یک داده بزرگ را از هاب تهیه کنید -* چگونه از API سطح بالای "Trainer" آموزش دهنده برای بازتنظیم مدل استفاده کنید +* چگونه از API سطح بالای "Trainer" برای بازتنظیم مدل استفاده کنید * چگونه یک چرخه تعلیم دلخواه درست کنید -* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه تعلیم دلخواه را در هر تنظیمات غیر متمرکزی استفاده کنید +* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید {:else} From 34eeeda61e79b6473aa4c91f3e58482fe4512229 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 13:39:56 +0200 Subject: [PATCH 020/789] section 2 - process data - file added. --- chapters/fa/chapter3/1.mdx | 2 + chapters/fa/chapter3/2.mdx | 383 +++++++++++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 chapters/fa/chapter3/2.mdx diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index ccb01c2e9..b960c3886 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,3 +1,5 @@ + + # مقدمه در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدلهای پیش تعلیم را جهت انجام پیش بینی های جدید بررسی کردیم. اما چگونه میتوانید یک مدل پیش تعلیم را خودتان بازتنظیم کنید؟ diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx new file mode 100644 index 000000000..4611e8469 --- /dev/null +++ b/chapters/fa/chapter3/2.mdx @@ -0,0 +1,383 @@ + + +#پردازش داده +# Processing the data + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +ادامه از مثال [فصل قبل](/course/chapter2) نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch به شرح زیر میباش: +Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one 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} +Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one 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} + +Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. + +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 + +{#if fw === 'pt'} + +{:else} + +{/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 🤗 Datasets library provides a very simple command to download and cache a dataset on the Hub. We can download the MRPC dataset like this: + +```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 + }) +}) +``` + +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. + +We can access each pair of sentences in our `raw_datasets` object by indexing, like with a 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 .'} +``` + +We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: + +```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)} +``` + +Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers to label name is stored in the *names* folder. `0` corresponds to `not_equivalent`, and `1` corresponds to `equivalent`. + + + +✏️ **Try it out!** Look at element 15 of the training set and element 87 of the validation set. What are their labels? + + + +### Preprocessing a dataset + +{#if fw === 'pt'} + +{:else} + +{/if} + +To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: + +```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"]) +``` + +However, we can't just pass two sequences to the model and get a prediction of whether the two sentences are paraphrases or not. We need to handle the two sequences as a pair, and apply the appropriate preprocessing. Fortunately, the tokenizer can also take a pair of sequences and prepare it the way our BERT model expects: + +```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] +} +``` + +We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/chapter2), but we put off talking about `token_type_ids`. In this example, this is what tells the model which part of the input is the first sentence and which is the second sentence. + + + +✏️ **Try it out!** Take element 15 of the training set and tokenize the two sentences separately and as a pair. What's the difference between the two results? + + + +If we decode the IDs inside `input_ids` back to words: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +we will get: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: + +```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] +``` + +As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. + +Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. + +Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. + +With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. + +In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. + +Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +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: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. + +Note that we've left the `padding` argument out in our tokenization function for now. This is because padding all the samples to the maximum length is not efficient: it's better to pad the samples when we're building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths! + +Here is how we apply the tokenization function on all our datasets at once. We're using `batched=True` in our call to `map` so the function is applied to multiple elements of our dataset at once, and not on each element separately. This allows for faster preprocessing. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: + +```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 + }) +}) +``` + +You can even use multiprocessing when applying your preprocessing function with `map()` by passing along a `num_proc` argument. We didn't do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing. + +Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. + +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 + + + +{#if fw === 'pt'} +The function that is responsible for putting together samples inside a batch is called a *collate function*. It's an argument you can pass when you build a `DataLoader`, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. + +{:else} + +The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. + +{/if} + +To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: + +{#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} + +To test this new toy, let's grab a few samples from our training set that we would like to batch together. Here, we remove the columns `idx`, `sentence1`, and `sentence2` as they won't be needed and contain strings (and we can't create tensors with strings) and have a look at the lengths of each entry in the 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] +``` + +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. Without dynamic padding, all of the samples would have to be padded to the maximum length in the whole dataset, or the maximum length the model can accept. Let's double-check that our `data_collator` is dynamically padding the batch properly: + +```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])} +``` + +Looking good! Now that we've gone from raw text to batches our model can deal with, we're ready to fine-tune it! + +{/if} + + + +✏️ **Try it out!** Replicate the preprocessing on the GLUE SST-2 dataset. It's a little bit different since it's composed of single sentences instead of pairs, but the rest of what we did should look the same. For a harder challenge, try to write a preprocessing function that works on any of the GLUE tasks. + + + +{#if fw === 'tf'} + +Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our 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, +) +``` + +And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. + +{/if} From 4abbc2a52c44cab2fce0fe339cc5912f253efa09 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 13:40:44 +0200 Subject: [PATCH 021/789] minor fix --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 4611e8469..507f255f4 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,6 +1,6 @@ -#پردازش داده +# پردازش داده # Processing the data {#if fw === 'pt'} From 2217041e8b682e894775bccbcd4866c3be575d29 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 13:45:56 +0200 Subject: [PATCH 022/789] minor fix --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 507f255f4..7efd875be 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -24,7 +24,9 @@ {/if} {#if fw === 'pt'} -ادامه از مثال [فصل قبل](/course/chapter2) نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch به شرح زیر میباش: + + در این بخش در ادامه مثال [فصل قبل](/course/chapter2) نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: + Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one batch in PyTorch: ```python From 7b4590533776df7fc9e3c03ed6cb6995b02cec7c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 13:46:48 +0200 Subject: [PATCH 023/789] minor fix --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 7efd875be..fc88d8348 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -25,7 +25,7 @@ {#if fw === 'pt'} - در این بخش در ادامه مثال [فصل قبل](/course/chapter2) نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: + در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one batch in PyTorch: From bf53804b3a176cf46035589d160a7e55a64a28a7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 13:56:52 +0200 Subject: [PATCH 024/789] added new paragraph --- chapters/fa/chapter3/2.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index fc88d8348..c5444dce9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,8 +27,6 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: -Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one batch in PyTorch: - ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -52,7 +50,8 @@ loss.backward() optimizer.step() ``` {:else} -Continuing with the example from the [previous chapter](/course/chapter2), here is how we would train a sequence classifier on one batch in TensorFlow: + +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: ```python import tensorflow as tf @@ -74,8 +73,13 @@ model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` + {/if} +البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی داده بزرگتر خواهید داشت. + +در این بخش ما از داده -- که در مقاله -- معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. ما این داده را به این دلیل انتخاب کردیم که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. + Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. 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. From 5b85ac44680b0e94e6dcd86bc85c427a06b6a523 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 18:19:57 +0200 Subject: [PATCH 025/789] urls added --- chapters/fa/chapter3/2.mdx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c5444dce9..c3127e372 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,12 +78,15 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی داده بزرگتر خواهید داشت. -در این بخش ما از داده -- که در مقاله -- معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. ما این داده را به این دلیل انتخاب کردیم که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) by William B. Dolan and Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. ما این داده را به این دلیل انتخاب کردیم که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. 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 {#if fw === 'pt'} @@ -92,6 +95,8 @@ 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 🤗 Datasets library provides a very simple command to download and cache a dataset on the Hub. We can download the MRPC dataset like this: From 18ed2b6c79b034131316e462dd0829dc5ae33d50 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 18:45:33 +0200 Subject: [PATCH 026/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c3127e372..2332412f9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -95,7 +95,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/if} -هاب تنها شامل مدلها نمیشود؛ بلکه شامل داده های متعدد در بسیاری زبانهای مختلف می باشد. در اینجا شما میتوانید +هاب تنها شامل مدلها نمیشود؛ بلکه شامل داده های متعدد در بسیاری زبانهای مختلف می باشد. شما میتوانید داده ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می کنیم پس از اتمام این این بخش یک داده جدید را بارگذاری و پردازش کنید (بخش داکیومنتهای عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) ببنید). اما اجازه بدید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده GLUE benchmark](https://gluebenchmark.com/) است که یک بنچمارک اکادمی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ وظیفه دسته بندی متن مختلف میباشد. 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. From 53af6411071aa8b0cb3da7d908ab55797d4178b7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 18:47:14 +0200 Subject: [PATCH 027/789] typos fixed --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 2332412f9..6ab0537f4 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -95,7 +95,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) ببنید). اما اجازه بدید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک بنچمارک اکادمی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ وظیفه دسته بندی متن مختلف میباشد. 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. From 8d386faa31f2cf8b53fbcf47d0a82de46b9efd3a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 18:48:44 +0200 Subject: [PATCH 028/789] writing improved --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 6ab0537f4..33b9a4462 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -95,7 +95,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک بنچمارک اکادمی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ وظیفه دسته بندی متن مختلف میباشد. 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. From 991055c0471f15148c8dd1299b1cd6346e0bac2a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 18:56:11 +0200 Subject: [PATCH 029/789] synch with glossary --- chapters/fa/chapter3/2.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 33b9a4462..e207d93ce 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -99,6 +99,8 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras 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. +کتابخانه داده 🤗 یک دستور بسیار ساده جهت دانلود و بارگذاری یک داده در هاب ارائه میکند. ما میتوانیم داده MRPC را به صورت زیر دانلود کنیم. + 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: ```py From 9a6023e81ac6dfad278a3c8e672e06ee71690206 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 19:05:51 +0200 Subject: [PATCH 030/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e207d93ce..6ad194308 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -99,7 +99,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras 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. -کتابخانه داده 🤗 یک دستور بسیار ساده جهت دانلود و بارگذاری یک داده در هاب ارائه میکند. ما میتوانیم داده MRPC را به صورت زیر دانلود کنیم. +کتابخانه داده 🤗 یک دستور بسیار ساده جهت دانلود و بارگذاری یک داده در هاب ارائه میکند. ما میتوانیم داده MRPC را به روش زیر دانلود کنیم. 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: @@ -127,8 +127,12 @@ DatasetDict({ }) ``` +همانطور که می بینید یک ابجکت "DatasetDict" بدست می آوریم که شامل مجموعه داده های Train, Test, و Validation میباشد. هریک از اینها شامل چندین سطون (`sentence1`, `sentence2`, `label`, and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می دهند میباشد. + 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). +این دستور داده را به صورت پیش فرض در *~/.cache/huggingface/dataset* دانلود و کش میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید دایرکتوری کشتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + 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. We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: From 4f1d096cfead622d9c9097b653cc0bcc79d30fd6 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 19:08:40 +0200 Subject: [PATCH 031/789] polished sentences --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 6ad194308..3f4372212 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی داده بزرگتر خواهید داشت. -در این بخش ما از داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) by William B. Dolan and Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. ما این داده را به این دلیل انتخاب کردیم که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) by William B. Dolan and Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این داده این است که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 3544c2903a69622ae9309a9d5d99948d14e54f16 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 20 Apr 2022 19:13:24 +0200 Subject: [PATCH 032/789] new sentence added --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3f4372212..aeca47bff 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک ابجکت "DatasetDict" بدست می آوریم که شامل مجموعه داده های Train, Test, و Validation میباشد. هریک از اینها شامل چندین سطون (`sentence1`, `sentence2`, `label`, and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می دهند میباشد. +همانطور که می بینید یک ابجکت "DatasetDict" بدست می آوریم که شامل مجموعه داده های Train Test, و Validation میباشد. هریک از اینها شامل چندین سطون (`sentence1`, `sentence2`, `label`, and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می دهند میباشد. 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). @@ -135,6 +135,8 @@ As you can see, we get a `DatasetDict` object which contains the training set, t 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. +ما می توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از indexing درست مانند یک dictionary دسترسی پیدا کنیم: + We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: ```py From e297990bbe2c6304d4b006905b9929d4b2abf570 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 08:43:15 +0200 Subject: [PATCH 033/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index aeca47bff..5b6c40429 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -135,7 +135,7 @@ As you can see, we get a `DatasetDict` object which contains the training set, t 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. -ما می توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از indexing درست مانند یک dictionary دسترسی پیدا کنیم: +ما می توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از indexing, مانند یک dictionary دسترسی پیدا کنیم: We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: @@ -151,6 +151,8 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` +ما می‌توانیم ببینم که برچسبها از پیش مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، ما می‌توانیم ویژگی‌های `raw_train_dataset`‌مان را بررسی کنیم. + We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: ```py From d279e146735c9d338d3adb2b8e3a8fdf43dcdb8b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 09:03:01 +0200 Subject: [PATCH 034/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5b6c40429..e2247f80d 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -ما می‌توانیم ببینم که برچسبها از پیش مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، ما می‌توانیم ویژگی‌های `raw_train_dataset`‌مان را بررسی کنیم. +می‌توانیم از پیش ببینم که برچسبها مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، ما می‌توانیم ویژگی‌های `raw_train_dataset`‌مان را بررسی کنیم. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: @@ -166,6 +166,8 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` +در پیش صحنه، `برچسب` از نوع `برچسب کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. + Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers to label name is stored in the *names* folder. `0` corresponds to `not_equivalent`, and `1` corresponds to `equivalent`. From 104acb966821eb857141dd801717c4b5ebc0c9c2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:15:46 +0200 Subject: [PATCH 035/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e2247f80d..e8590aa97 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -166,12 +166,14 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` -در پیش صحنه، `برچسب` از نوع `برچسب کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. +در پشت صحنه، `برچسب` از نوع `برچسب کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers to label name is stored in the *names* folder. `0` corresponds to `not_equivalent`, and `1` corresponds to `equivalent`. +✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده --- و عنصر شماره ۸۷ از داده --- را مشاهده کنید. برچسبهای آنها چیست؟ + ✏️ **Try it out!** Look at element 15 of the training set and element 87 of the validation set. What are their labels? From cc15095fb9df63d99e82687b75b8a6f09d851bb2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:22:21 +0200 Subject: [PATCH 036/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e8590aa97..3301a3a9f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -174,6 +174,12 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers ✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده --- و عنصر شماره ۸۷ از داده --- را مشاهده کنید. برچسبهای آنها چیست؟ + + + + +✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده --- و عنصر شماره ۸۷ از داده --- را مشاهده کنید. برچسبهای آنها چیست؟ + ✏️ **Try it out!** Look at element 15 of the training set and element 87 of the validation set. What are their labels? From e9e9250b63f884ec319ce34388e6bbe3244d759e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:23:12 +0200 Subject: [PATCH 037/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3301a3a9f..ad80ef599 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -172,14 +172,12 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده --- و عنصر شماره ۸۷ از داده --- را مشاهده کنید. برچسبهای آنها چیست؟ +✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ -✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده --- و عنصر شماره ۸۷ از داده --- را مشاهده کنید. برچسبهای آنها چیست؟ - ✏️ **Try it out!** Look at element 15 of the training set and element 87 of the validation set. What are their labels? From a98899d93f7ce919764dd91c9ad0b87654901927 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:29:19 +0200 Subject: [PATCH 038/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index ad80ef599..0e763c406 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -182,6 +182,8 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers +### پیش پردازشِ یک داده + ### Preprocessing a dataset {#if fw === 'pt'} @@ -190,6 +192,8 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} +به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در فصل قبل مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم را به صورت زیر توکنایز کنیم: + To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: ```py From 3c7526d27cb365d2c09fa4e4e7eb400b580cd146 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:39:46 +0200 Subject: [PATCH 039/789] new paragraph added --- chapters/fa/chapter3/2.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 0e763c406..fa5c5b229 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -192,7 +192,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} -به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در فصل قبل مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم را به صورت زیر توکنایز کنیم: +به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2), مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: @@ -205,6 +205,8 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` +با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیشبینی کند متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکنایز می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده سازی کند: + However, we can't just pass two sequences to the model and get a prediction of whether the two sentences are paraphrases or not. We need to handle the two sequences as a pair, and apply the appropriate preprocessing. Fortunately, the tokenizer can also take a pair of sequences and prepare it the way our BERT model expects: ```py From f1ed412a35c288766bd4d5bfbb31119ed2fb007a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 18:40:59 +0200 Subject: [PATCH 040/789] fixed typo --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index fa5c5b229..a3be1e26d 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -192,7 +192,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} -به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2), مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: From 7b758748e3a0f5b075130912d066569cd300ac26 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 19:28:48 +0200 Subject: [PATCH 041/789] multiple paragraphs first draft added --- chapters/fa/chapter3/2.mdx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index a3be1e26d..399588ba2 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -172,7 +172,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -✏️ **خودتان امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ +✏️ **امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ @@ -222,26 +222,36 @@ inputs } ``` +در فصل ۲ ما در مورد `input_ids` شری `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. + We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/chapter2), but we put off talking about `token_type_ids`. In this example, this is what tells the model which part of the input is the first sentence and which is the second sentence. +✏️ **امتحان کنید!** عنصر شماره ۱۵ داده را بردارید و دو جمله را به صورت جداگانه و جفت توکنایز کنید. تفاوت دو نتیجه چیست؟ + ✏️ **Try it out!** Take element 15 of the training set and tokenize the two sentences separately and as a pair. What's the difference between the two results? +اگر آیدی های داخل `input_ids` را رمزگشایی کنیم: + If we decode the IDs inside `input_ids` back to words: ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` +خواهیم داشت: + we will get: ```python out ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` +بنابر این می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. + So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: ```python out @@ -249,16 +259,28 @@ So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` +همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` میباشند همگی دارای آیدی نوع توکن `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند همگی دارای آیدی نوع توکن `1` میباشند. + As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. +توجه داشته باشید که اگر چکپوینت دیگری را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل میداند با آنها چکار کند، به این خاطر که آنها را در زمان پیش تعلیم دیده است. + Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. +در اینجا، --- با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در فصل --- در مورد آن صحبت کردیم، این مدل یک وظیفه دیگ تحت عنوان ---- دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد،. + Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. + در روش پیش بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده اند) به مدل داده می‌شود و از آن خواسته می‌شود که پیش بینی کند که آیا جمله دوم در ادامه جمله اول قرار دارد یا خیر.برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها جمله‌ها در متن اصلی به دنبال هم آمده‌اند، و در نیمی دیگر دو جمله از دو متن متفاوت می‌آیند. + With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. +در مجموع، نیازی نیست نگران وجود یا عدم وجود --- در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چراکه توکنایزر میداند چه چیزی برای مدل تهیه کند. + In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت از جملات برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ داده training اینگونه می‌باشد: + Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: ```py From 5f09dc4ecdff84c1f2dfaf435032cbb21a58da33 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 21 Apr 2022 22:05:36 +0200 Subject: [PATCH 042/789] polish --- chapters/fa/chapter3/2.mdx | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 399588ba2..f79bafe58 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -25,7 +25,7 @@ {#if fw === 'pt'} - در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: + در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می‌دهیم: ```python import torch @@ -51,7 +51,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می‌دهیم: ```python import tensorflow as tf @@ -76,9 +76,9 @@ model.train_on_batch(batch, labels) {/if} -البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی داده بزرگتر خواهید داشت. +البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) by William B. Dolan and Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب میباشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این داده این است که داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) نوشته William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. @@ -95,11 +95,11 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. 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. -کتابخانه داده 🤗 یک دستور بسیار ساده جهت دانلود و بارگذاری یک داده در هاب ارائه میکند. ما میتوانیم داده MRPC را به روش زیر دانلود کنیم. +کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه میکند. ما میتوانیم مجموعه داده MRPC را به روش زیر دانلود کنیم. 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: @@ -131,7 +131,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). -این دستور داده را به صورت پیش فرض در *~/.cache/huggingface/dataset* دانلود و کش میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید دایرکتوری کشتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* دانلود و ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. @@ -182,7 +182,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -### پیش پردازشِ یک داده +### پیش پردازشِ یک مجموعه داده ### Preprocessing a dataset @@ -192,7 +192,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} -به منظور پیش بردازش داد، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش بردازش مجموعه داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: @@ -222,7 +222,7 @@ inputs } ``` -در فصل ۲ ما در مورد `input_ids` شری `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. +در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/chapter2), but we put off talking about `token_type_ids`. In this example, this is what tells the model which part of the input is the first sentence and which is the second sentence. @@ -259,27 +259,27 @@ So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` میباشند همگی دارای آیدی نوع توکن `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند همگی دارای آیدی نوع توکن `1` میباشند. +همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` می‌باشند همگی دارای آیدی نوع توکن `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند همگی دارای آیدی نوع توکن `1` می‌باشند. As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. -توجه داشته باشید که اگر چکپوینت دیگری را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل میداند با آنها چکار کند، به این خاطر که آنها را در زمان پیش تعلیم دیده است. +توجه داشته باشید که اگر چکپوینت دیگری را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. -در اینجا، --- با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در فصل --- در مورد آن صحبت کردیم، این مدل یک وظیفه دیگ تحت عنوان ---- دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد،. +در اینجا، BERT با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در فصل --- در مورد آن صحبت کردیم، این مدل یک وظیفه دیگر تحت عنوان _next sentence prediction_ دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. - در روش پیش بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده اند) به مدل داده می‌شود و از آن خواسته می‌شود که پیش بینی کند که آیا جمله دوم در ادامه جمله اول قرار دارد یا خیر.برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها جمله‌ها در متن اصلی به دنبال هم آمده‌اند، و در نیمی دیگر دو جمله از دو متن متفاوت می‌آیند. + در روش پیش‌ بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شود و از آن خواسته می‌شود که پیش بینی کند آیا جمله دوم در ادامه جمله اول قرار دارد یا خیر.برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها جمله‌ها در متن اصلی به دنبال هم آمده‌اند، و در نیمی دیگر دو جمله از دو متن متفاوت می‌آیند. With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. -در مجموع، نیازی نیست نگران وجود یا عدم وجود --- در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چراکه توکنایزر میداند چه چیزی برای مدل تهیه کند. +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چراکه توکنایزر میداند چه چیزی برای مدل تهیه کند. In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت از جملات برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ داده training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ مجموعه داده training اینگونه می‌باشد: Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: @@ -291,9 +291,12 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` +این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (شامل کلیدها، `input_ids`, `attention_mask`, and `token_type_ids`, و مقادیری که مجموعه ای از مجموعه ها هستند). همچنین این روش فقط زمانی کار میکند که حافظه موقت کافی جهت ذخیره سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کردید نگه میدارید. ) 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: ```py From cb38ccb54b3073bd4ade7167434528e5d0ab01d7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 22 Apr 2022 08:49:51 +0200 Subject: [PATCH 043/789] new paragraphs added --- chapters/fa/chapter3/2.mdx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f79bafe58..8e48007c1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -291,11 +291,11 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (شامل کلیدها، `input_ids`, `attention_mask`, and `token_type_ids`, و مقادیری که مجموعه ای از مجموعه ها هستند). همچنین این روش فقط زمانی کار میکند که حافظه موقت کافی جهت ذخیره سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کردید نگه میدارید. ) +این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (شامل کلیدها، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیری که مجموعه ای از مجموعه ها هستند). همچنین این روش فقط زمانی کار میکند که حافظه موقت کافی جهت ذخیره سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کردید نگه میدارید. ) 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). - +به منظور نگه داشتن داده به صورت یک مجموعه داده از تابع [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) استفاده میکنیم. این روش همچنین به ما انعطافپذیری می‌دهد چنانچه به پیش پردازشهای بیشتری نیاز داشته باشیم. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: @@ -304,8 +304,12 @@ def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` +این تابع یک دیکشنری (مثل اقلام داخل مجموعه داده) دریافت میکند و دیکشنری دیگری بازمی‌گرداند با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`. توجه داشته باشید از انجایی که توکنایزر بر روی لیستهایی از جفت جمله ها کار میکند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار میکند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. + This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. + + Note that we've left the `padding` argument out in our tokenization function for now. This is because padding all the samples to the maximum length is not efficient: it's better to pad the samples when we're building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths! Here is how we apply the tokenization function on all our datasets at once. We're using `batched=True` in our call to `map` so the function is applied to multiple elements of our dataset at once, and not on each element separately. This allows for faster preprocessing. From ec10da95c14601b7fafefa2cb1ad73dfd3054f20 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 22 Apr 2022 09:16:09 +0200 Subject: [PATCH 044/789] new paragraphs added --- chapters/fa/chapter3/2.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 8e48007c1..af458dce3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -308,16 +308,19 @@ def tokenize_function(example): This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. - +توجه داشته باشید ما مبحث `padding` را در حال حاضر کنار گذاشته ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` روی نمونه‌ها را زمانی که در حال ساختن بتچ هستیم انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متقیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. Note that we've left the `padding` argument out in our tokenization function for now. This is because padding all the samples to the maximum length is not efficient: it's better to pad the samples when we're building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths! +در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه داده به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابر این تابع بر روی چندین عنصر از مجموعه داده ما به یکباره عمل می‌کند، نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش پردازش سریعتر انجام گیرد: + Here is how we apply the tokenization function on all our datasets at once. We're using `batched=True` in our call to `map` so the function is applied to multiple elements of our dataset at once, and not on each element separately. This allows for faster preprocessing. ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` +کتابخانه مجموعه داده 🤗 این پیش پردازش را با افزودن -فیلدهای- جدید به مجموعه داده‌ها، یکی به ازای هر کلید در -دیکشنری- که توسط تابع پیش پردازش باز گردانده می‌شود اعمال می‌کند، The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: From 06fc68f5b608cec259c02e6a1689fb6708cdf899 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 22 Apr 2022 10:56:35 +0200 Subject: [PATCH 045/789] new paragraphs added --- chapters/fa/chapter3/2.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index af458dce3..c7fe4f65e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -341,10 +341,16 @@ DatasetDict({ }) ``` +شما حتی می‌توانید زمانی که تابع پیش پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایز سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش پردازش شما را سریعتر کند. + You can even use multiprocessing when applying your preprocessing function with `map()` by passing along a `num_proc` argument. We didn't do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخشهای مجموعه داده افزوده گردند. توجه داشته باشید که اگر تابع پیش پردازش ما برای یک کلید موجود در مجموعه داده که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گرداند همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. + Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. +آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* می‌گوییم. + 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 From f61f8b7cf6b565fbfc08f2b481667a808c9a6829 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 22 Apr 2022 16:05:06 +0200 Subject: [PATCH 046/789] new paragraphs added --- chapters/fa/chapter3/2.mdx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c7fe4f65e..8524392aa 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -349,19 +349,26 @@ You can even use multiprocessing when applying your preprocessing function with Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. -آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* می‌گوییم. +آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* (هم طول کردن پویا) می‌گوییم. 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 {#if fw === 'pt'} + +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم طول سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم طول سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم طول سازی اضافه داشته باشد. + The function that is responsible for putting together samples inside a batch is called a *collate function*. It's an argument you can pass when you build a `DataLoader`, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. {:else} +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم طول سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم طول سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم طول سازی اضافه داشته باشد. + The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. {/if} From 7f2a8bbb24659506a89f01e6989c73d8aef56ffc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 22 Apr 2022 17:14:41 +0200 Subject: [PATCH 047/789] first draft done. --- chapters/fa/chapter3/2.mdx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 8524392aa..51c2598f2 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -349,7 +349,7 @@ You can even use multiprocessing when applying your preprocessing function with Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. -آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* (هم طول کردن پویا) می‌گوییم. +آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* (همطول سازی پویا) می‌گوییم. 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*. @@ -373,6 +373,8 @@ The function that is responsible for putting together samples inside a batch is {/if} +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم طول سازی را به آیتمهای مجموعه داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: + To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: {#if fw === 'pt'} @@ -389,6 +391,8 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" ``` {/if} +اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا سطون‌های -، --، و --- را خذف می‌کنیم چراکه احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (و ما نمی‌توانیم تنسورهایی با رشته های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: + To test this new toy, let's grab a few samples from our training set that we would like to batch together. Here, we remove the columns `idx`, `sentence1`, and `sentence2` as they won't be needed and contain strings (and we can't create tensors with strings) and have a look at the lengths of each entry in the batch: ```py @@ -401,6 +405,8 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se [50, 59, 47, 67, 59, 50, 62, 32] ``` +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. همطول سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، همطول شده باشند. بدون همطول سازی پویا، همه نمونه‌ها در کل مجموعه داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، همطول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی همطول می‌کند: + 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. Without dynamic padding, all of the samples would have to be padded to the maximum length in the whole dataset, or the maximum length the model can accept. Let's double-check that our `data_collator` is dynamically padding the batch properly: ```py @@ -426,18 +432,24 @@ batch = data_collator(samples) 'labels': torch.Size([8])} ``` +به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل مان می‌تواند با آنها کار کند، آماده هستیم برای انجام بازتنظیم مدل: + Looking good! Now that we've gone from raw text to batches our model can deal with, we're ready to fine-tune it! {/if} +✏️ **امتحان کنید!** پروسه پیش بردازش را روی مجموعه داده GLUE SST-2 باز تکرار کنید. این یک مقدار متفاوت است از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد، اما بقیه کارهایی که انجام دادیم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. + ✏️ **Try it out!** Replicate the preprocessing on the GLUE SST-2 dataset. It's a little bit different since it's composed of single sentences instead of pairs, but the rest of what we did should look the same. For a harder challenge, try to write a preprocessing function that works on any of the GLUE tasks. {#if fw === 'tf'} +توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه داده‌مان مشاهده کنیم! + Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! ```py @@ -458,6 +470,8 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` +این هم از این! ما می‌توانی آن مجموعه داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش پردازش به طرز خوشایندی سرراست خواهد بود. + And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. {/if} From f5aa7689d0c6f25d0ccfc49c6f32f1a31c47362d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 09:56:44 +0200 Subject: [PATCH 048/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 51c2598f2..07112d48c 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -25,7 +25,7 @@ {#if fw === 'pt'} - در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: ```python import torch @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf) نوشته William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، MRPC نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From a93d7a20555992a33cfb9228a6ea85bd3f53ee8c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 09:57:52 +0200 Subject: [PATCH 049/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 07112d48c..ee1fb467c 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، MRPC نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From ede676eeff9ef15dd011c1b572ea9132e01de84f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 09:58:49 +0200 Subject: [PATCH 050/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index ee1fb467c..5252a0ea9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 113d77aa2d118ee72a17716059df2731bf7a0ae9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 09:59:48 +0200 Subject: [PATCH 051/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5252a0ea9..ee1fb467c 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 2f4a662264bfd44bcaeab89546ac96d1f7341a76 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:00:58 +0200 Subject: [PATCH 052/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index ee1fb467c..c22ac970a 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده (Microsoft Research Paraphrase Corpus) MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC یا (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 4dd8c5def92ab4a50326f259c45f0c4bfa872887 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:01:46 +0200 Subject: [PATCH 053/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c22ac970a..5252a0ea9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC یا (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 33c8b39612aa7960d903ad0c2be854d1b6f45739 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:02:26 +0200 Subject: [PATCH 054/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5252a0ea9..bd9e9d859 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,7 +78,7 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett. معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. From 54a7614bd287cf60c7c3f2c6bfd63eb8cd12927c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:15:46 +0200 Subject: [PATCH 055/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bd9e9d859..d90da1c68 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -78,14 +78,14 @@ model.train_on_batch(batch, labels) البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) است. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. 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 @@ -95,11 +95,11 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. 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. -کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه میکند. ما میتوانیم مجموعه داده MRPC را به روش زیر دانلود کنیم. +کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه میکند. ما میتوانیم مجموعه داده MRPC را به روش زیر دانلود کنیم: 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: @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک ابجکت "DatasetDict" بدست می آوریم که شامل مجموعه داده های Train Test, و Validation میباشد. هریک از اینها شامل چندین سطون (`sentence1`, `sentence2`, `label`, and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می دهند میباشد. +همانطور که می بینید یک شیء "DatasetDict" بدست می آوریم که شامل مجموعه داده‌های Train، Test، و Validation می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From 56ed2ea801feb76650b7616583993cad49879cb2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:18:21 +0200 Subject: [PATCH 056/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d90da1c68..72cabfe2f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می آوریم که شامل مجموعه داده‌های Train، Test، و Validation می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، and `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه داده‌های Train، Test، و Validation می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، و `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From e342854b21f64b794d5f83369727947eedb86db7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:21:33 +0200 Subject: [PATCH 057/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 72cabfe2f..f131ded76 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه داده‌های Train، Test، و Validation می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، و `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، و `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From c28091e7ba787e8307c02536bb7dc37fa5a82d45 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:24:44 +0200 Subject: [PATCH 058/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f131ded76..717195c66 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، `label`، و `idx`) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (-، --، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From da86f2a9f47b51d6732939789692aec946ebb7a0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:25:54 +0200 Subject: [PATCH 059/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 717195c66..428486c49 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (-، --، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، --، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From c9680ebe411ca411459e676d85bba820ebdbef5b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:26:34 +0200 Subject: [PATCH 060/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 428486c49..5d225f4ad 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، --، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From a27f1ba4737e88263b14007a1faa4c2314ad7a6b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:28:59 +0200 Subject: [PATCH 061/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5d225f4ad..45f0c1efe 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`sentence1`، `sentence2`، ---، و ----) و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx` و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). 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). From 66efcf3ae8c8ebb5bd40aac2eaa128c02afc8d18 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 24 Apr 2022 10:30:42 +0200 Subject: [PATCH 062/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 45f0c1efe..51f17ed03 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx` و تعداد متغیری ردیف داده که تعداد عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test وجود دارند.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test.). 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). From 2b0e4a600fc82bae81e6a0cafb511c0d2ea30819 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:33:08 +0200 Subject: [PATCH 063/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 51f17ed03..cc3984948 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -127,7 +127,7 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test.). +همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test). 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). From af4de38f6010f2b9190cec718bc2a6ec05a2bd82 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:40:27 +0200 Subject: [PATCH 064/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cc3984948..21fd54fd6 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* دانلود و ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From 9c1276fa0be704d32dd9cb42df26a88a9f33df0b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:42:07 +0200 Subject: [PATCH 065/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 21fd54fd6..d5a4a7694 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From 5b79da9a816b76515ed150550c4a1b3e1b79d16c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:43:26 +0200 Subject: [PATCH 066/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d5a4a7694..cf18eba9f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *dataset/huggingface/.cache/~* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From b0fd16396b9c73cac21335c6f31d5b54cea24c3e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:45:00 +0200 Subject: [PATCH 067/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cf18eba9f..1b0f22ed1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *dataset/huggingface/.cache/~* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From 217073a21d55f37b286bf5fd1a09d68e99788ddb Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:46:11 +0200 Subject: [PATCH 068/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 1b0f22ed1..294372d99 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی */.cache/huggingface/dataset~* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From 1a6bd700a16e245a09fe66eff10c6c97c0f64f22 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:49:20 +0200 Subject: [PATCH 069/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 294372d99..0e46cdf29 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی */.cache/huggingface/dataset~* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *`~/.cache/huggingface/dataset`* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From 15f7f953fbdd9688edccb9b6ed0f64eb90736ddf Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:50:51 +0200 Subject: [PATCH 070/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 0e46cdf29..1b0f22ed1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *`~/.cache/huggingface/dataset`* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. From a4c9c1782fdaace56b71588b18f6747d6e3fd4a6 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 08:58:33 +0200 Subject: [PATCH 071/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 1b0f22ed1..b588825e3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -131,11 +131,11 @@ 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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که شما میتوانید زیرشاخه‌ی ذخیره سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. -ما می توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از indexing, مانند یک dictionary دسترسی پیدا کنیم: +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از شماره‌گذاری, مانند یک dictionary دسترسی پیدا کنیم: We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: From 868402c2e1f6f3604eed056903b00e1c196b26b1 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:06:12 +0200 Subject: [PATCH 072/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index b588825e3..494bc9957 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -99,7 +99,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras 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. -کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه میکند. ما میتوانیم مجموعه داده MRPC را به روش زیر دانلود کنیم: +کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه داده MRPC را به روش زیر دانلود کنیم: 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: @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌توانیم از پیش ببینم که برچسبها مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، ما می‌توانیم ویژگی‌های `raw_train_dataset`‌مان را بررسی کنیم. +می‌توانیم از پیش ببینم که برچسبها مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: From 4598b69bf80829b16e14702303024b55798d29a7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:12:19 +0200 Subject: [PATCH 073/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 494bc9957..a26f3c77c 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌توانیم از پیش ببینم که برچسبها مقادیر عددی صحیح هستند، بنابراین نیازی به پیش پردازش آنها نداریم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی در آنجا انجام دهیم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: From 87942d6c092ffbf519b13637fffcc63f7b3b7242 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:18:30 +0200 Subject: [PATCH 074/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index a26f3c77c..50f50c9fc 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -234,7 +234,7 @@ We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/ch -اگر آیدی های داخل `input_ids` را رمزگشایی کنیم: +اگر آیدی های داخل `input_ids` را به کلمات رمزگشایی کنیم: If we decode the IDs inside `input_ids` back to words: @@ -250,7 +250,7 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابر این می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `sentence1 [SEP] sentence2 [SEP] [CLS]` باشند. So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: From 1971f779f972ac80b3cb47ffcbfe7e5dd2ed4609 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:19:24 +0200 Subject: [PATCH 075/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 50f50c9fc..bbbff15b5 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -250,7 +250,7 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `sentence1 [SEP] sentence2 [SEP] [CLS]` باشند. +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: From a1d8859afcb8ad6b8aace09b3f611521e1306333 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:20:54 +0200 Subject: [PATCH 076/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bbbff15b5..50f50c9fc 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -250,7 +250,7 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `sentence1 [SEP] sentence2 [SEP] [CLS]` باشند. So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: From b5ebbc96095b4336c2551e58e041c76ae6c0ea2d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 09:23:12 +0200 Subject: [PATCH 077/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 50f50c9fc..b622527cb 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -250,7 +250,7 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `sentence1 [SEP] sentence2 [SEP] [CLS]` باشند. +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `a [CLS] sentence1 [SEP] sentence2 [SEP]` باشند. So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: From 4811b944bf166d551c229ae33c2a2f022d243dc5 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 11:30:20 +0200 Subject: [PATCH 078/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index b622527cb..c606aa67f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -250,7 +250,7 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `a [CLS] sentence1 [SEP] sentence2 [SEP]` باشند. +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: @@ -259,7 +259,7 @@ So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` می‌باشند همگی دارای آیدی نوع توکن `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند همگی دارای آیدی نوع توکن `1` می‌باشند. +همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` می‌باشند آیدی نشاندهنده نوع توکِن آنها `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند آیدی نشاندهنده نوع توکِن آنها `1` می‌باشند. As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. @@ -267,7 +267,7 @@ As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. -در اینجا، BERT با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در فصل --- در مورد آن صحبت کردیم، این مدل یک وظیفه دیگر تحت عنوان _next sentence prediction_ دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، BERT با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در [فصل 1](/course/chapter1) در مورد آن صحبت کردیم، این مدل یک وظیفه دیگر تحت عنوان _next sentence prediction_ دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. From cc392b970a97ae8e9cd6555d570e4163a766654a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 11:31:39 +0200 Subject: [PATCH 079/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c606aa67f..bb6406e07 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی در آنجا انجام دهیم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: From a6b5e07ffcf274cefd28384864298a3a4bc97683 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 11:52:39 +0200 Subject: [PATCH 080/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bb6406e07..e154869c3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: @@ -166,7 +166,7 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` -در پشت صحنه، `برچسب` از نوع `برچسب کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. +در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers to label name is stored in the *names* folder. `0` corresponds to `not_equivalent`, and `1` corresponds to `equivalent`. @@ -182,7 +182,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -### پیش پردازشِ یک مجموعه داده +### پیش‌پردازشِ یک مجموعه داده ### Preprocessing a dataset @@ -192,7 +192,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} -به منظور پیش بردازش مجموعه داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش‌پردازش مجموعه داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: @@ -205,7 +205,7 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` -با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیشبینی کند متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکنایز می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده سازی کند: +با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده سازی کند: However, we can't just pass two sequences to the model and get a prediction of whether the two sentences are paraphrases or not. We need to handle the two sequences as a pair, and apply the appropriate preprocessing. Fortunately, the tokenizer can also take a pair of sequences and prepare it the way our BERT model expects: @@ -222,7 +222,7 @@ inputs } ``` -در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. +در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی، جمله اول و کدام بخش جمله دوم است. We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/chapter2), but we put off talking about `token_type_ids`. In this example, this is what tells the model which part of the input is the first sentence and which is the second sentence. @@ -259,15 +259,15 @@ So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` می‌باشند آیدی نشاندهنده نوع توکِن آنها `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند آیدی نشاندهنده نوع توکِن آنها `1` می‌باشند. +همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند آیدی نشاندهنده نوع توکِن آنها `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند آیدی نشاندهنده نوع توکِن‌شان `1` می‌باشد. As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. -توجه داشته باشید که اگر چکپوینت دیگری را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. +توجه داشته باشید که اگر چکپوینت متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. -در اینجا، BERT با آیدی های نوع توکن پیش‌تعلیم شده، و در بالای هدف مدل زبانی ماسکی که در [فصل 1](/course/chapter1) در مورد آن صحبت کردیم، این مدل یک وظیفه دیگر تحت عنوان _next sentence prediction_ دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف masked language modeling که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنان _next sentence prediction_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. From 0bcc2f6c0ef3c3a39113053b2792aa8fc7819c36 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:01:57 +0200 Subject: [PATCH 081/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e154869c3..0378e831d 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -267,11 +267,11 @@ As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. -در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف masked language modeling که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنان _next sentence prediction_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبان ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. - در روش پیش‌ بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شود و از آن خواسته می‌شود که پیش بینی کند آیا جمله دوم در ادامه جمله اول قرار دارد یا خیر.برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها جمله‌ها در متن اصلی به دنبال هم آمده‌اند، و در نیمی دیگر دو جمله از دو متن متفاوت می‌آیند. + در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ی جمله‌ی اول قرار دارد یا خیر. برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. From edc0f359608a8bb02567624d6a9d2dc2cbf43077 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:08:51 +0200 Subject: [PATCH 082/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 0378e831d..786a5355f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -271,15 +271,15 @@ Note that if you select a different checkpoint, you won't necessarily have the ` Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. - در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ی جمله‌ی اول قرار دارد یا خیر. برای خارج کردن وظیفه از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. + در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ی جمله‌ی اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. -در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چراکه توکنایزر میداند چه چیزی برای مدل تهیه کند. +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ مجموعه داده training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ مجموعه داده‌ی training اینگونه می‌باشد: Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: @@ -291,7 +291,7 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (شامل کلیدها، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیری که مجموعه ای از مجموعه ها هستند). همچنین این روش فقط زمانی کار میکند که حافظه موقت کافی جهت ذخیره سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کردید نگه میدارید. ) +این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقدار‌ها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کرده‌اید نگه می‌دارید. ) 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). From c3cf4e79cc853446d35a1ffe327139ac7ca9ddd2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:16:08 +0200 Subject: [PATCH 083/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 786a5355f..bf35fb0e9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -95,7 +95,7 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. 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. @@ -151,7 +151,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه‌داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: @@ -267,19 +267,19 @@ As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. -در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبان ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبانِ ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. - در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ی جمله‌ی اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. + در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. -در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی های توکنیزه شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکنایز شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک راه برای پیش پردازشِ مجموعه داده‌ی training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه‌داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ مجموعه‌داده‌ی training اینگونه می‌باشد: Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: @@ -291,7 +291,7 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این به خوبی کار میکند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقدار‌ها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه داده‌های موجود در پایگاه مجموعه داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کرده‌اید نگه می‌دارید. ) +این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کرده‌اید نگه می‌دارید. ) 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). @@ -320,7 +320,7 @@ Here is how we apply the tokenization function on all our datasets at once. We'r tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -کتابخانه مجموعه داده 🤗 این پیش پردازش را با افزودن -فیلدهای- جدید به مجموعه داده‌ها، یکی به ازای هر کلید در -دیکشنری- که توسط تابع پیش پردازش باز گردانده می‌شود اعمال می‌کند، +کتابخانه مجموعه داده 🤗 این پیش پردازش را با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به ازای هر کلید در -دیکشنری- که توسط تابع پیش پردازش باز گردانده می‌شود اعمال می‌کند، The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: @@ -373,7 +373,7 @@ The function that is responsible for putting together samples inside a batch is {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم طول سازی را به آیتمهای مجموعه داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم طول سازی را به آیتمهای مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! @@ -470,7 +470,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! ما می‌توانی آن مجموعه داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! ما می‌توانی آن مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش پردازش به طرز خوشایندی سرراست خواهد بود. And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. From c0a6bd929e47d601032d6a7afdf5a7d208b2c8a0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:19:50 +0200 Subject: [PATCH 084/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bf35fb0e9..cd3792401 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازشهای بیشتری علاوه‌بر توکنیزشن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: From 234ef87bd387f687a0e48d968ac12acc4bf01df4 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:22:25 +0200 Subject: [PATCH 085/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cd3792401..715f933c7 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map()` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: From 1e5ecfe24511502343e7ff09b0c7376f88b0db66 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:22:55 +0200 Subject: [PATCH 086/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 715f933c7..32c6a4a73 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: From b9080795c3ebcbdb4ecd71c79fa3d4e956922fd2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:23:20 +0200 Subject: [PATCH 087/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 32c6a4a73..06bfbc9e2 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: From 28e160db630268daa2f918b29325edfdaa483fc0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:24:13 +0200 Subject: [PATCH 088/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 06bfbc9e2..587078b0f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: From d6aa6d632878da889deed1d3156bfb4da10027e8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:25:04 +0200 Subject: [PATCH 089/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 587078b0f..c600f9f8d 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map` با اعمال کردن یک عملیات روی هر عنصر مجموعه داده عمل میکند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی ها را توکنایز کند: 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: @@ -304,7 +304,7 @@ def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -این تابع یک دیکشنری (مثل اقلام داخل مجموعه داده) دریافت میکند و دیکشنری دیگری بازمی‌گرداند با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`. توجه داشته باشید از انجایی که توکنایزر بر روی لیستهایی از جفت جمله ها کار میکند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار میکند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. +این تابع یک دیکشنری (مثل اقلام داخل مجموعه داده) دریافت میکند و دیکشنری دیگری بازمی‌گرداند با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`. توجه داشته باشید از انجایی که توکنایزر بر روی لیستهایی از جفت جمله ها کار میکند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار میکند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. From 5b852113c6e97784f61206ad13a15c942aba74a7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:37:26 +0200 Subject: [PATCH 090/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c600f9f8d..cf5e0cdb5 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -76,9 +76,9 @@ model.train_on_batch(batch, labels) {/if} -البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه داده بزرگتر خواهید داشت. +البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه‌داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه داده این است که مجموعه داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه‌داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه آموزش دادن روی آن آسان است. Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. @@ -95,11 +95,11 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. 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. -کتابخانه مجموعه داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه داده MRPC را به روش زیر دانلود کنیم: +کتابخانه مجموعه‌داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه‌داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه‌داده MRPC را به روش زیر دانلود کنیم: 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: @@ -131,7 +131,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). - این دستور مجموعه داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور مجموعه‌داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. 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. @@ -182,7 +182,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -### پیش‌پردازشِ یک مجموعه داده +### پیش‌پردازشِ یک مجموعه‌داده ### Preprocessing a dataset @@ -192,7 +192,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers {/if} -به منظور پیش‌پردازش مجموعه داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش‌پردازش مجموعه‌داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: @@ -295,7 +295,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). -به منظور نگه داشتن داده به صورت یک مجموعه‌داده از تابع [`()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.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map` با اعمال کردن یک عملیات روی هر عنصر مجموعه‌داده عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکنایز کند: 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: @@ -304,15 +304,15 @@ def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -این تابع یک دیکشنری (مثل اقلام داخل مجموعه داده) دریافت میکند و دیکشنری دیگری بازمی‌گرداند با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`. توجه داشته باشید از انجایی که توکنایزر بر روی لیستهایی از جفت جمله ها کار میکند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار میکند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. +این تابع یک دیکشنری (مثل اقلام داخل مجموعه‌داده) دریافت میکند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids` بازمی‌گرداند. توجه داشته باشید از آنجایی که توکنایزر روی لیستهایی از جفت جمله‌ها کار می‌کند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. -توجه داشته باشید ما مبحث `padding` را در حال حاضر کنار گذاشته ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` روی نمونه‌ها را زمانی که در حال ساختن بتچ هستیم انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متقیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه‌داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. Note that we've left the `padding` argument out in our tokenization function for now. This is because padding all the samples to the maximum length is not efficient: it's better to pad the samples when we're building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths! -در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه داده به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابر این تابع بر روی چندین عنصر از مجموعه داده ما به یکباره عمل می‌کند، نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش پردازش سریعتر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه‌داده به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از مجموعه‌داده ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: Here is how we apply the tokenization function on all our datasets at once. We're using `batched=True` in our call to `map` so the function is applied to multiple elements of our dataset at once, and not on each element separately. This allows for faster preprocessing. @@ -320,7 +320,7 @@ Here is how we apply the tokenization function on all our datasets at once. We'r tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -کتابخانه مجموعه داده 🤗 این پیش پردازش را با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به ازای هر کلید در -دیکشنری- که توسط تابع پیش پردازش باز گردانده می‌شود اعمال می‌کند، +کتابخانه مجموعه‌داده 🤗 این پیش‌پردازش را با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شود اعمال می‌کند، The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: @@ -341,11 +341,11 @@ DatasetDict({ }) ``` -شما حتی می‌توانید زمانی که تابع پیش پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایز سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش پردازش شما را سریعتر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایز سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. You can even use multiprocessing when applying your preprocessing function with `map()` by passing along a `num_proc` argument. We didn't do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخشهای مجموعه داده افزوده گردند. توجه داشته باشید که اگر تابع پیش پردازش ما برای یک کلید موجود در مجموعه داده که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گرداند همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخشهای مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گرداند همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. @@ -405,7 +405,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] ``` -تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. همطول سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، همطول شده باشند. بدون همطول سازی پویا، همه نمونه‌ها در کل مجموعه داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، همطول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی همطول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. همطول سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، همطول شده باشند. بدون همطول سازی پویا، همه نمونه‌ها در کل مجموعه‌داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، همطول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی همطول می‌کند: 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. Without dynamic padding, all of the samples would have to be padded to the maximum length in the whole dataset, or the maximum length the model can accept. Let's double-check that our `data_collator` is dynamically padding the batch properly: @@ -440,7 +440,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi -✏️ **امتحان کنید!** پروسه پیش بردازش را روی مجموعه داده GLUE SST-2 باز تکرار کنید. این یک مقدار متفاوت است از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد، اما بقیه کارهایی که انجام دادیم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش بردازش را روی مجموعه‌داده GLUE SST-2 باز تکرار کنید. این یک مقدار متفاوت است از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد، اما بقیه کارهایی که انجام دادیم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. ✏️ **Try it out!** Replicate the preprocessing on the GLUE SST-2 dataset. It's a little bit different since it's composed of single sentences instead of pairs, but the rest of what we did should look the same. For a harder challenge, try to write a preprocessing function that works on any of the GLUE tasks. @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! @@ -470,7 +470,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! ما می‌توانی آن مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! ما می‌توانی آن مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. From 4848b6126eec9489613d4887bf2551d3111ba559 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 12:48:07 +0200 Subject: [PATCH 091/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cf5e0cdb5..f94b59f58 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -320,7 +320,7 @@ Here is how we apply the tokenization function on all our datasets at once. We'r tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -کتابخانه مجموعه‌داده 🤗 این پیش‌پردازش را با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شود اعمال می‌کند، +روشی که کتابخانه مجموعه‌داده 🤗 این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: @@ -341,19 +341,19 @@ DatasetDict({ }) ``` -شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایز سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. You can even use multiprocessing when applying your preprocessing function with `map()` by passing along a `num_proc` argument. We didn't do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخشهای مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گرداند همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. -آخرین چیزی که نیاز داریم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر پَد کنیم - تکنیکی که ما به آن *dynamic padding* (همطول سازی پویا) می‌گوییم. +آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر همطول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. 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 @@ -361,19 +361,19 @@ The last thing we will need to do is pad all the examples to the length of the l {#if fw === 'pt'} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم طول سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم طول سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم طول سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. The function that is responsible for putting together samples inside a batch is called a *collate function*. It's an argument you can pass when you build a `DataLoader`, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. {:else} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم طول سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم طول سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم طول سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم طول سازی را به آیتمهای مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتمهای مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: From d4eb913cbff9d286396d5bc61f14a42cff94df15 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:01:06 +0200 Subject: [PATCH 092/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f94b59f58..c9887c2e5 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -349,7 +349,7 @@ You can even use multiprocessing when applying your preprocessing function with Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. -آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر همطول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. +آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. 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*. @@ -361,7 +361,7 @@ The last thing we will need to do is pad all the examples to the length of the l {#if fw === 'pt'} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. The function that is responsible for putting together samples inside a batch is called a *collate function*. It's an argument you can pass when you build a `DataLoader`, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. @@ -373,7 +373,7 @@ The function that is responsible for putting together samples inside a batch is {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتمهای مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا معرفی کنید (که چه توکنی برای همطول سازی استفاده کند، و اینکه مدل انتظار همطول سازی از سمت چپ ورودی‌ها را دارد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: @@ -391,7 +391,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" ``` {/if} -اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا سطون‌های -، --، و --- را خذف می‌کنیم چراکه احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (و ما نمی‌توانیم تنسورهایی با رشته های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: +اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا سطون‌های `idx`، `sentence1`، و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: To test this new toy, let's grab a few samples from our training set that we would like to batch together. Here, we remove the columns `idx`, `sentence1`, and `sentence2` as they won't be needed and contain strings (and we can't create tensors with strings) and have a look at the lengths of each entry in the batch: @@ -405,7 +405,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] ``` -تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. همطول سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، همطول شده باشند. بدون همطول سازی پویا، همه نمونه‌ها در کل مجموعه‌داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، همطول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی همطول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل مجموعه‌داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: 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. Without dynamic padding, all of the samples would have to be padded to the maximum length in the whole dataset, or the maximum length the model can accept. Let's double-check that our `data_collator` is dynamically padding the batch properly: From ddf951a3121defedc202fbbf056cc3e8e339c532 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:08:39 +0200 Subject: [PATCH 093/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c9887c2e5..cd0a19be3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -440,7 +440,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi -✏️ **امتحان کنید!** پروسه پیش بردازش را روی مجموعه‌داده GLUE SST-2 باز تکرار کنید. این یک مقدار متفاوت است از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد، اما بقیه کارهایی که انجام دادیم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی مجموعه‌داده GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. ✏️ **Try it out!** Replicate the preprocessing on the GLUE SST-2 dataset. It's a little bit different since it's composed of single sentences instead of pairs, but the rest of what we did should look the same. For a harder challenge, try to write a preprocessing function that works on any of the GLUE tasks. @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌ داده مان و یک collator داریم، حال نیاز داریم که آنها را بهم وصل کنیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این کار زیادی می‌برد و احتمالا خیلی بهینه هم نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! From f65bf3e03409cdc40b8caedf52c4c09fd2f62d30 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:10:31 +0200 Subject: [PATCH 094/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cd0a19be3..604af7b95 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `()model.fit` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! From 3253b83e187dd435fd928d7769598b9d2decab39 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:11:23 +0200 Subject: [PATCH 095/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 604af7b95..8e2ef1c79 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `()model.fit` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `tf.data.Dataset` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! From b9e4251a81fa4758e9d345f6b991bef981c6c9ad Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:13:48 +0200 Subject: [PATCH 096/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 8e2ef1c79..8027786c4 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -448,7 +448,7 @@ Looking good! Now that we've gone from raw text to batches our model can deal wi {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه این یک تابع به سرعت یک مجموعه‌داده 🤗 را به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک مجموعه‌داده 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! @@ -470,7 +470,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! ما می‌توانی آن مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختیهای پروسه پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! حالا می‌توانیم این مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. From 21a79479c5a0c7c1fa9f670cc30e234d7e0eb777 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:23:37 +0200 Subject: [PATCH 097/789] minor fixes and polish --- chapters/fa/chapter3/2.mdx | 93 ++------------------------------------ 1 file changed, 3 insertions(+), 90 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 8027786c4..486aefcda 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -80,11 +80,6 @@ model.train_on_batch(batch, labels) در این بخش ما از مجموعه‌داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه آموزش دادن روی آن آسان است. -Of course, just training the model on two sentences is not going to yield very good results. To get better results, you will need to prepare a bigger dataset. - -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 @@ -97,12 +92,8 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras هاب تنها شامل مدلها نمی‌باشد؛ بلکه شامل مجموعه‌داده‌های متعدد در بسیاری زبانهای مختلف می‌باشد. شما می‌توانید مجموعه‌داده‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک مجموعه‌داده جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. -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. - کتابخانه مجموعه‌داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه‌داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه‌داده MRPC را به روش زیر دانلود کنیم: -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: - ```py from datasets import load_dataset @@ -129,16 +120,10 @@ DatasetDict({ همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test). -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). - این دستور مجموعه‌داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. -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. - ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از شماره‌گذاری, مانند یک dictionary دسترسی پیدا کنیم: -We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: - ```py raw_train_dataset = raw_datasets["train"] raw_train_dataset[0] @@ -153,8 +138,6 @@ raw_train_dataset[0] می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه‌داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. -We can see the labels are already integers, so we won't have to do any preprocessing there. To know which integer corresponds to which label, we can inspect the `features` of our `raw_train_dataset`. This will tell us the type of each column: - ```py raw_train_dataset.features ``` @@ -168,24 +151,12 @@ raw_train_dataset.features در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. -Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers to label name is stored in the *names* folder. `0` corresponds to `not_equivalent`, and `1` corresponds to `equivalent`. - - ✏️ **امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ - - - - - -✏️ **Try it out!** Look at element 15 of the training set and element 87 of the validation set. What are their labels? - ### پیش‌پردازشِ یک مجموعه‌داده -### Preprocessing a dataset - {#if fw === 'pt'} {:else} @@ -194,8 +165,6 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers به منظور پیش‌پردازش مجموعه‌داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: -To preprocess the dataset, we need to convert the text to numbers the model can make sense of. As you saw in the [previous chapter](/course/chapter2), this is done with a tokenizer. We can feed the tokenizer one sentence or a list of sentences, so we can directly tokenize all the first sentences and all the second sentences of each pair like this: - ```py from transformers import AutoTokenizer @@ -207,8 +176,6 @@ tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده سازی کند: -However, we can't just pass two sequences to the model and get a prediction of whether the two sentences are paraphrases or not. We need to handle the two sequences as a pair, and apply the appropriate preprocessing. Fortunately, the tokenizer can also take a pair of sequences and prepare it the way our BERT model expects: - ```py inputs = tokenizer("This is the first sentence.", "This is the second one.") inputs @@ -224,19 +191,13 @@ inputs در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی، جمله اول و کدام بخش جمله دوم است. -We discussed the `input_ids` and `attention_mask` keys in [Chapter 2](/course/chapter2), but we put off talking about `token_type_ids`. In this example, this is what tells the model which part of the input is the first sentence and which is the second sentence. - ✏️ **امتحان کنید!** عنصر شماره ۱۵ داده را بردارید و دو جمله را به صورت جداگانه و جفت توکنایز کنید. تفاوت دو نتیجه چیست؟ -✏️ **Try it out!** Take element 15 of the training set and tokenize the two sentences separately and as a pair. What's the difference between the two results? - -اگر آیدی های داخل `input_ids` را به کلمات رمزگشایی کنیم: - -If we decode the IDs inside `input_ids` back to words: +اگر آیدی‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) @@ -252,8 +213,6 @@ we will get: بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. -So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] sentence2 [SEP]` when there are two sentences. Aligning this with the `token_type_ids` gives us: - ```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] @@ -261,28 +220,16 @@ So we see the model expects the inputs to be of the form `[CLS] sentence1 [SEP] همانطور که می‌بینید، بخشهایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند آیدی نشاندهنده نوع توکِن آنها `0` و بخشهایی که مربوط به `sentence2 [SEP]` هستند آیدی نشاندهنده نوع توکِن‌شان `1` می‌باشد. -As you can see, the parts of the input corresponding to `[CLS] sentence1 [SEP]` all have a token type ID of `0`, while the other parts, corresponding to `sentence2 [SEP]`, all have a token type ID of `1`. - توجه داشته باشید که اگر چکپوینت متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. -Note that if you select a different checkpoint, you won't necessarily have the `token_type_ids` in your tokenized inputs (for instance, they're not returned if you use a DistilBERT model). They are only returned when the model will know what to do with them, because it has seen them during its pretraining. - در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبانِ ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. -Here, BERT is pretrained with token type IDs, and on top of the masked language modeling objective we talked about in [Chapter 1](/course/chapter1), it has an additional objective called _next sentence prediction_. The goal with this task is to model the relationship between pairs of sentences. - در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. -With next sentence prediction, the model is provided pairs of sentences (with randomly masked tokens) and asked to predict whether the second sentence follows the first. To make the task non-trivial, half of the time the sentences follow each other in the original document they were extracted from, and the other half of the time the two sentences come from two different documents. - در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکنایز شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. -In general, you don't need to worry about whether or not there are `token_type_ids` in your tokenized inputs: as long as you use the same checkpoint for the tokenizer and the model, everything will be fine as the tokenizer knows what to provide to its model. - اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه‌داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ مجموعه‌داده‌ی training اینگونه می‌باشد: -Now that we have seen how our tokenizer can deal with one pair of sentences, we can use it to tokenize our whole dataset: like in the [previous chapter](/course/chapter2), we can feed the tokenizer a list of pairs of sentences by giving it the list of first sentences, then the list of second sentences. This is also compatible with the padding and truncation options we saw in [Chapter 2](/course/chapter2). So, one way to preprocess the training dataset is: - ```py tokenized_dataset = tokenizer( raw_datasets["train"]["sentence1"], @@ -291,39 +238,27 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره سازی در حافظه درخواست کرده‌اید نگه می‌دارید. ) - -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). +این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه می‌دارید). به منظور نگه داشتن داده به صورت یک مجموعه‌داده، از تابع [`()Dataset.map`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) استفاده میکنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `()map` با اعمال کردن یک عملیات روی هر عنصر مجموعه‌داده عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکنایز کند: -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: - ```py def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -این تابع یک دیکشنری (مثل اقلام داخل مجموعه‌داده) دریافت میکند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids` بازمی‌گرداند. توجه داشته باشید از آنجایی که توکنایزر روی لیستهایی از جفت جمله‌ها کار می‌کند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. - -This function takes a dictionary (like the items of our dataset) and returns a new dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`. Note that it also works if the `example` dictionary contains several samples (each key as a list of sentences) since the `tokenizer` works on lists of pairs of sentences, as seen before. This will allow us to use the option `batched=True` in our call to `map()`, which will greatly speed up the tokenization. The `tokenizer` is backed by a tokenizer written in Rust from the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library. This tokenizer can be very fast, but only if we give it lots of inputs at once. +این تابع یک دیکشنری (مثل اقلام داخل مجموعه‌داده) دریافت میکند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids` بازمی‌گرداند. توجه داشته باشید از آنجایی که توکنایزر روی لیستهایی از جفت جمله‌ها کار می‌کند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه‌داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. -Note that we've left the `padding` argument out in our tokenization function for now. This is because padding all the samples to the maximum length is not efficient: it's better to pad the samples when we're building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths! - در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه‌داده به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از مجموعه‌داده ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: -Here is how we apply the tokenization function on all our datasets at once. We're using `batched=True` in our call to `map` so the function is applied to multiple elements of our dataset at once, and not on each element separately. This allows for faster preprocessing. - ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` روشی که کتابخانه مجموعه‌داده 🤗 این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، -The way the 🤗 Datasets library applies this processing is by adding new fields to the datasets, one for each key in the dictionary returned by the preprocessing function: - ```python out DatasetDict({ train: Dataset({ @@ -343,15 +278,11 @@ DatasetDict({ شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. -You can even use multiprocessing when applying your preprocessing function with `map()` by passing along a `num_proc` argument. We didn't do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing. تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. -Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attention_mask`, and `token_type_ids`, so those three fields are added to all splits of our dataset. Note that we could also have changed existing fields if our preprocessing function returned a new value for an existing key in the dataset to which we applied `map()`. - آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. -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*. ### هم‌طول‌سازی پویا @@ -363,20 +294,14 @@ The last thing we will need to do is pad all the examples to the length of the l تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. -The function that is responsible for putting together samples inside a batch is called a *collate function*. It's an argument you can pass when you build a `DataLoader`, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. - {:else} تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. -The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. - {/if} برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: -To do this in practice, we have to define a collate function that will apply the correct amount of padding to the items of the dataset we want to batch together. Fortunately, the 🤗 Transformers library provides us with such a function via `DataCollatorWithPadding`. It takes a tokenizer when you instantiate it (to know which padding token to use, and whether the model expects padding to be on the left or on the right of the inputs) and will do everything you need: - {#if fw === 'pt'} ```py from transformers import DataCollatorWithPadding @@ -393,8 +318,6 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا سطون‌های `idx`، `sentence1`، و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: -To test this new toy, let's grab a few samples from our training set that we would like to batch together. Here, we remove the columns `idx`, `sentence1`, and `sentence2` as they won't be needed and contain strings (and we can't create tensors with strings) and have a look at the lengths of each entry in the batch: - ```py samples = tokenized_datasets["train"][:8] samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} @@ -407,8 +330,6 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل مجموعه‌داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: -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. Without dynamic padding, all of the samples would have to be padded to the maximum length in the whole dataset, or the maximum length the model can accept. Let's double-check that our `data_collator` is dynamically padding the batch properly: - ```py batch = data_collator(samples) {k: v.shape for k, v in batch.items()} @@ -434,24 +355,18 @@ batch = data_collator(samples) به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل مان می‌تواند با آنها کار کند، آماده هستیم برای انجام بازتنظیم مدل: -Looking good! Now that we've gone from raw text to batches our model can deal with, we're ready to fine-tune it! - {/if} ✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی مجموعه‌داده GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. -✏️ **Try it out!** Replicate the preprocessing on the GLUE SST-2 dataset. It's a little bit different since it's composed of single sentences instead of pairs, but the rest of what we did should look the same. For a harder challenge, try to write a preprocessing function that works on any of the GLUE tasks. - {#if fw === 'tf'} توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک مجموعه‌داده 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! -Now that we have our dataset and a data collator, we need to put them together. We could manually load batches and collate them, but that's a lot of work, and probably not very performant either. Instead, there's a simple method that offers a performant solution to this problem: `to_tf_dataset()`. This will wrap a `tf.data.Dataset` around your dataset, with an optional collation function. `tf.data.Dataset` is a native TensorFlow format that Keras can use for `model.fit()`, so this one method immediately converts a 🤗 Dataset to a format that's ready for training. Let's see it in action with our dataset! - ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( columns=["attention_mask", "input_ids", "token_type_ids"], @@ -472,6 +387,4 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( این هم از این! حالا می‌توانیم این مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. -And that's it! We can take those datasets forward into the next lecture, where training will be pleasantly straightforward after all the hard work of data preprocessing. - {/if} From 1dd88fe6373dc3406b5c276048fc43b6cf1945a0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:24:26 +0200 Subject: [PATCH 098/789] minor fix --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 486aefcda..e1cef18a8 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -51,7 +51,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بتچ توسط pytorch را شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: ```python import tensorflow as tf From 1ab29f4e3bee3c5914b8692ce8fc315b9abf61ff Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:28:28 +0200 Subject: [PATCH 099/789] Ch3 - 1 and 2 funal draft. --- chapters/fa/chapter3/1.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index b960c3886..6cfa5d3ee 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -2,22 +2,22 @@ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدلهای پیش تعلیم را جهت انجام پیش بینی های جدید بررسی کردیم. اما چگونه میتوانید یک مدل پیش تعلیم را خودتان بازتنظیم کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل پیش‌تعلیم را خودتان بازتنظیم کنید؟ {#if fw === 'pt'} -* چگونه یک داده بزرگ را از هاب تهیه کنید +* چگونه یک مجموعه‌داده بزرگ را از هاب تهیه کنید * چگونه از API سطح بالای "Trainer" برای بازتنظیم مدل استفاده کنید -* چگونه یک چرخه تعلیم دلخواه درست کنید -* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید +* چگونه یک چرخه‌تعلیم دلخواه درست کنید +* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه‌تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید {:else} -* چگونه یک داده بزرگ را از هاب تهیه کنید -* چگونه از کراس برای بازتنظیم مدل استفاده کنید -* چگونه از کراس برای استخراج پیش بینی ها استفاده کنید +* چگونه یک مجموعه‌داده بزرگ را از هاب تهیه کنید +* چگونه از کِراس برای بازتنظیم مدل استفاده کنید +* چگونه از کِراس برای استخراج پیش بینی‌ها استفاده کنید * چگونه از یک متریک دلخواه استفاده کنید {/if} -جهت بارگذاری چک پوینت تعلیم دیده خود در هاب هاگینک فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) \ No newline at end of file +جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) \ No newline at end of file From e1fdabc51fdfb206d67ac67da5c41d6242a4986f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 13:34:47 +0200 Subject: [PATCH 100/789] Ch3 - 1 and 2 funal draft. --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e1cef18a8..2a683ac84 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -90,7 +90,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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. کتابخانه مجموعه‌داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه‌داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه‌داده MRPC را به روش زیر دانلود کنیم: From cfc8ba62688a8b5536f228185ab477e80a53eae9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 25 Apr 2022 21:40:50 +0200 Subject: [PATCH 101/789] Ch3 - 1 and 2 final draft. --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 2a683ac84..5be25d104 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -365,7 +365,7 @@ batch = data_collator(samples) {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک مجموعه‌داده 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل می‌کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک مجموعه‌داده 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( From fa0a0479a80b768aff8852b7234a6651fc776abd Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:02:48 +0800 Subject: [PATCH 102/789] Complete the translation of Chapters 0 and 2 --- chapters/zh/_toctree.yml | 26 ++- chapters/zh/chapter0/1.mdx | 110 ++++++++++++ chapters/zh/chapter2/1.mdx | 21 +++ chapters/zh/chapter2/2.mdx | 353 ++++++++++++++++++++++++++++++++++++ chapters/zh/chapter2/3.mdx | 262 +++++++++++++++++++++++++++ chapters/zh/chapter2/4.mdx | 239 +++++++++++++++++++++++++ chapters/zh/chapter2/5.mdx | 355 +++++++++++++++++++++++++++++++++++++ chapters/zh/chapter2/6.mdx | 165 +++++++++++++++++ chapters/zh/chapter2/7.mdx | 27 +++ chapters/zh/chapter2/8.mdx | 293 ++++++++++++++++++++++++++++++ 10 files changed, 1850 insertions(+), 1 deletion(-) create mode 100644 chapters/zh/chapter0/1.mdx create mode 100644 chapters/zh/chapter2/1.mdx create mode 100644 chapters/zh/chapter2/2.mdx create mode 100644 chapters/zh/chapter2/3.mdx create mode 100644 chapters/zh/chapter2/4.mdx create mode 100644 chapters/zh/chapter2/5.mdx create mode 100644 chapters/zh/chapter2/6.mdx create mode 100644 chapters/zh/chapter2/7.mdx create mode 100644 chapters/zh/chapter2/8.mdx diff --git a/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml index 453fc5e99..26a1bf6e9 100644 --- a/chapters/zh/_toctree.yml +++ b/chapters/zh/_toctree.yml @@ -1,3 +1,8 @@ +- title: 0. 安装 + sections: + - local: chapter0/1 + title: 简介 + - title: 1. Transformer 模型 sections: - local: chapter1/1 @@ -20,4 +25,23 @@ title: 总结 - local: chapter1/10 title: 章末小测验 - quiz: 1 \ No newline at end of file + quiz: 1 +- title: 2. 使用 🤗 Transformers + sections: + - local: chapter2/1 + title: 介绍 + - local: chapter2/2 + title: 管道的内部 + - local: chapter2/3 + title: 模型 + - local: chapter2/4 + title: 标记器(Tokenizer) + - local: chapter2/5 + title: 处理多个序列 + - local: chapter2/6 + title: 把它们放在一起 + - local: chapter2/7 + title: 基本用法完成! + - local: chapter2/8 + title: 章末小测试 + quiz: 2 \ No newline at end of file diff --git a/chapters/zh/chapter0/1.mdx b/chapters/zh/chapter0/1.mdx new file mode 100644 index 000000000..ca2294a22 --- /dev/null +++ b/chapters/zh/chapter0/1.mdx @@ -0,0 +1,110 @@ +# 简介 + +欢迎来到拥抱脸课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 + +我们将在本课程中使用的所有库都以 Python 包的形式提供,因此在这里我们将向您展示如何设置 Python 环境并安装您需要的特定库。 + +我们将介绍两种设置工作环境的方法,使用 Colab 笔记本或 Python 虚拟环境。选择任意一种趁手的方式即可。对于初学者,我们强烈建议您从使用 Colab 笔记本开始(国内无法访问,可以跳过,直接查阅本页**安装依赖**那一节即可在本地的环境下运行,译者注)。 + +请注意,Python 虚拟环境的一些命令不支持Windows系统。如果您在 Windows 上运行,我们建议您继续使用 Colab 笔记本。如果您使用的是 Linux 发行版或 macOS,则可以使用此处的任一方法。 + +大多数课程和服务都依赖于您拥有 Hugging Face 帐户。我们建议现在创建一个:[创建一个账号](https://huggingface.co/join). + +## 使用 Google Colab 笔记本 + +使用 Colab notebook 是最简单的设置;可以在浏览器中启动Notebook并直接开始编写自己的代码! + +如果您不熟悉 Colab,我们建议您从[这个介绍](https://colab.research.google.com/notebooks/intro.ipynb)开始。Colab 提供一些加速硬件,例如 GPU 或 TPU,并且当我们使用的算力比较少的时候是免费的。 + +当打开 Colab 后,创建一个新笔记本: + +
+An empty colab notebook +
+ +下一步是安装我们将在本课程中使用的库。我们将使用 **pip** 进行安装,它是 Python 的包管理器。在Notebook中,您可以通过加上!字符表示执行系统命令,所以安装🤗 Transformers 的命令如下: + +``` +!pip install transformers +``` + +您可以通过在运行 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。我们建议保持这个默认的Python安装程序没有任何包,当运行某个程序的时候就为那个程序创建一个单独的运行环境 - 这样,每个应用程序都可以有自己的依赖项和包,您无需担心与其他应用程序潜在的兼容性问题。 + +在 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* 的新目录: + +``` +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]" +``` + +您现在已准备就绪,可以开始了! \ No newline at end of file diff --git a/chapters/zh/chapter2/1.mdx b/chapters/zh/chapter2/1.mdx new file mode 100644 index 000000000..a5b7387b3 --- /dev/null +++ b/chapters/zh/chapter2/1.mdx @@ -0,0 +1,21 @@ +# 介绍 + +正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 + +创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: +- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 +- **灵活**:所有型号的核心都是简单的PyTorch +**nn.Module** 或者 TensorFlow **tf.kears.Mode1**,可以像它们各自的机器学习(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/chapter2/2.mdx b/chapters/zh/chapter2/2.mdx new file mode 100644 index 000000000..7d1922158 --- /dev/null +++ b/chapters/zh/chapter2/2.mdx @@ -0,0 +1,353 @@ + + +# 管道的内部 + +{#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/chapter2/3.mdx b/chapters/zh/chapter2/3.mdx new file mode 100644 index 000000000..8f3c703e2 --- /dev/null +++ b/chapters/zh/chapter2/3.mdx @@ -0,0 +1,262 @@ + + +# 模型 + +{#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/chapter2/4.mdx b/chapters/zh/chapter2/4.mdx new file mode 100644 index 000000000..fb4819296 --- /dev/null +++ b/chapters/zh/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/chapter2/5.mdx b/chapters/zh/chapter2/5.mdx new file mode 100644 index 000000000..1b73568ed --- /dev/null +++ b/chapters/zh/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*是与输入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/chapter2/6.mdx b/chapters/zh/chapter2/6.mdx new file mode 100644 index 000000000..e4b5c6295 --- /dev/null +++ b/chapters/zh/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/chapter2/7.mdx b/chapters/zh/chapter2/7.mdx new file mode 100644 index 000000000..1835352f6 --- /dev/null +++ b/chapters/zh/chapter2/7.mdx @@ -0,0 +1,27 @@ +# 基本用法完成! + +很好地完成了到这里的课程!总而言之,在本章中,您可以: + +- 学习了Transformers模型的基本构造块。 + + +- 了解了标记化管道的组成。 + + +- 了解了如何在实践中使用Transformers模型。 + + +- 学习了如何利用分词器将文本转换为模型可以理解的张量。 + + +- 将分词器和模型一起设置,以从文本到预测。 + + +- 了解了inputs IDs的局限性,并了解了attention mask。 + + +- 使用多功能和可配置的分词器方法。 + + + +从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 diff --git a/chapters/zh/chapter2/8.mdx b/chapters/zh/chapter2/8.mdx new file mode 100644 index 000000000..89cc36502 --- /dev/null +++ b/chapters/zh/chapter2/8.mdx @@ -0,0 +1,293 @@ + + + + +# 章末小测试 + +### 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} From fe1fdd20dbd35bce042a60e34de90e992a8fd526 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 27 Apr 2022 10:57:37 +0200 Subject: [PATCH 103/789] Ch3-P3 - first push --- chapters/fa/chapter3/3.mdx | 172 ++++++++++++++++ chapters/fa/chapter3/3_tf.mdx | 203 +++++++++++++++++++ chapters/fa/chapter3/4.mdx | 359 ++++++++++++++++++++++++++++++++++ chapters/fa/chapter3/5.mdx | 20 ++ chapters/fa/chapter3/6.mdx | 296 ++++++++++++++++++++++++++++ 5 files changed, 1050 insertions(+) create mode 100644 chapters/fa/chapter3/3.mdx create mode 100644 chapters/fa/chapter3/3_tf.mdx create mode 100644 chapters/fa/chapter3/4.mdx create mode 100644 chapters/fa/chapter3/5.mdx create mode 100644 chapters/fa/chapter3/6.mdx diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx new file mode 100644 index 000000000..749cfd511 --- /dev/null +++ b/chapters/fa/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# Fine-tuning a model with the Trainer API + + + + + +🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). + +The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: + +```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 + +The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) + + + +The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. + +Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `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, +) +``` + +Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! + +To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`: + +```py +trainer.train() +``` + +This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: + +1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). +2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). + + +### Evaluation + +Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. + +As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: + +```py +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: + +```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} +``` + +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. + +Wrapping everything together, we get our `compute_metrics()` function: + +```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) +``` + +And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: + +```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, +) +``` + +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: + +``` +trainer.train() +``` + +This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. + +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. + + + +✏️ **Try it out!** Fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. + + + diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx new file mode 100644 index 000000000..85a4626dd --- /dev/null +++ b/chapters/fa/chapter3/3_tf.mdx @@ -0,0 +1,203 @@ + + +# باز‌تنظیم یک مدل با استفاده از کِراس + +# Fine-tuning a model with Keras + + + +وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا آموزشِ مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU را انجام نداده‌اید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. + +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/). + +The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: + +```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 models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. + + + +That means that once we have our data, very little work is required to begin training on it. + + + +As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. + +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. + + + +Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. + + + +```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, +) +``` + + + +Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. + + + + +### Improving training performance + + + +If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause +is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes +that optimizer with default values for all parameters, including learning rate. From long experience, though, we know +that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written +as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. + +In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate +over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* +the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is +`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial +value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, +though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. + +```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) +``` + + + +The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. + + + +Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new 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"]) +``` + +Now, we fit again: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) + + + +### Model predictions + + + + +Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(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: + +```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} +``` + +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. diff --git a/chapters/fa/chapter3/4.mdx b/chapters/fa/chapter3/4.mdx new file mode 100644 index 000000000..54563ea7c --- /dev/null +++ b/chapters/fa/chapter3/4.mdx @@ -0,0 +1,359 @@ +# A full training + + + + + +Now we'll see how to achieve the same results as we did in the last section without using the `Trainer` class. Again, we assume you have done the data processing in section 2. Here is a short summary covering everything you will need: + +```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) +``` + +### Prepare for training + +Before actually writing our training loop, we will need to define a few objects. The first ones are the dataloaders we will use to iterate over batches. But before we can define those dataloaders, we need to apply a bit of postprocessing to our `tokenized_datasets`, to take care of some things that the `Trainer` did for us automatically. Specifically, we need to: + +- Remove the columns corresponding to values the model does not expect (like the `sentence1` and `sentence2` columns). +- Rename the column `label` to `labels` (because the model expects the argument to be named `labels`). +- Set the format of the datasets so they return PyTorch tensors instead of lists. + +Our `tokenized_datasets` has one method for each of those steps: + +```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 +``` + +We can then check that the result only has columns that our model will accept: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Now that this is done, we can easily define our 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 +) +``` + +To quickly check there is no mistake in the data processing, we can inspect a batch like this: + +```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])} +``` + +Note that the actual shapes will probably be slightly different for you since we set `shuffle=True` for the training dataloader and we are padding to the maximum length inside the batch. + +Now that we're completely finished with data preprocessing (a satisfying yet elusive goal for any ML practitioner), let's turn to the model. We instantiate it exactly as we did in the previous section: + +```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]) +``` + +All 🤗 Transformers models will return the loss when `labels` are provided, and we also get the logits (two for each input in our batch, so a tensor of size 8 x 2). + +We're almost ready to write our training loop! We're just missing two things: an optimizer and a learning rate scheduler. Since we are trying to replicate what the `Trainer` was doing by hand, we will use the same defaults. The optimizer used by the `Trainer` is `AdamW`, which is the same as Adam, but with a twist for weight decay regularization (see ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) by Ilya Loshchilov and Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Finally, the learning rate scheduler used by default is just a linear decay from the maximum value (5e-5) to 0. To properly define it, we need to know the number of training steps we will take, which is the number of epochs we want to run multiplied by the number of training batches (which is the length of our training dataloader). The `Trainer` uses three epochs by default, so we will follow that: + +```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 +``` + +### The training loop + +One last thing: we will want to use the GPU if we have access to one (on a CPU, training might take several hours instead of a couple of minutes). To do this, we define a `device` we will put our model and our batches on: + +```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') +``` + +We are now ready to train! To get some sense of when training will be finished, we add a progress bar over our number of training steps, using the `tqdm` library: + +```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) +``` + +You can see that the core of the training loop looks a lot like the one in the introduction. We didn't ask for any reporting, so this training loop will not tell us anything about how the model fares. We need to add an evaluation loop for that. + + +### 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: + +```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} +``` + +Again, your results will be slightly different because of the randomness in the model head initialization and the data shuffling, but they should be in the same ballpark. + + + +✏️ **Try it out!** Modify the previous training loop to fine-tune your model on the SST-2 dataset. + + + +### Supercharge your training loop with 🤗 Accelerate + + + +The training loop we defined earlier works fine on a single CPU or GPU. But using the [🤗 Accelerate](https://github.com/huggingface/accelerate) library, with just a few adjustments we can enable distributed training on multiple GPUs or TPUs. Starting from the creation of the training and validation dataloaders, here is what our manual training loop looks like: + +```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) +``` + +And here are the changes: + +```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) +``` + +The first line to add is the import line. The second line instantiates an `Accelerator` object that will look at the environment and initialize the proper distributed setup. 🤗 Accelerate handles the device placement for you, so you can remove the lines that put the model on the device (or, if you prefer, change them to use `accelerator.device` instead of `device`). + +Then the main bulk of the work is done in the line that sends the dataloaders, the model, and the optimizer to `accelerator.prepare()`. This will wrap those objects in the proper container to make sure your distributed training works as intended. The remaining changes to make are removing the line that puts the batch on the `device` (again, if you want to keep this you can just change it to use `accelerator.device`) and replacing `loss.backward()` with `accelerator.backward(loss)`. + + +⚠️ In order to benefit from the speed-up offered by Cloud TPUs, we recommend padding your samples to a fixed length with the `padding="max_length"` and `max_length` arguments of the tokenizer. + + +If you'd like to copy and paste it to play around, here's what the complete training loop looks like with 🤗 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) +``` + +Putting this in a `train.py` script will make that script runnable on any kind of distributed setup. To try it out in your distributed setup, run the command: + +```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 +``` + +which will launch the distributed training. + +If you want to try this in a Notebook (for instance, to test it with TPUs on Colab), just paste the code in a `training_function()` and run a last cell with: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +You can find more examples in the [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/fa/chapter3/5.mdx b/chapters/fa/chapter3/5.mdx new file mode 100644 index 000000000..dda8cb7fe --- /dev/null +++ b/chapters/fa/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# 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'} +* Learned about datasets in the [Hub](https://huggingface.co/datasets) +* Learned how to load and preprocess datasets, including using dynamic padding and collators +* Implemented your own fine-tuning and evaluation of a model +* Implemented a lower-level training loop +* Used 🤗 Accelerate to easily adapt your training loop so it works for multiple GPUs or TPUs + +{:else} +* Learned about datasets in the [Hub](https://huggingface.co/datasets) +* Learned how to load and preprocess datasets +* Learned how to fine-tune and evaluate a model with Keras +* Implemented a custom metric + +{/if} diff --git a/chapters/fa/chapter3/6.mdx b/chapters/fa/chapter3/6.mdx new file mode 100644 index 000000000..94d02da24 --- /dev/null +++ b/chapters/fa/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# 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? + + + +### 2. Search for the `ar_sarcasm` dataset in the [Hub](https://huggingface.co/datasets). Which task does it support? + +dataset card!" + }, + { + text: "Named entity recognition", + explain: "That's not it — take another look at the dataset card!" + }, + { + text: "Question answering", + explain: "Alas, this question was not answered correctly. Try again!" + } + ]} +/> + +### 3. How does the BERT model expect a pair of sentences to be processed? + +[SEP] special token is needed to separate the two sentences, but that's not the only thing!" + }, + { + text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2", + explain: "A [CLS] special token is required at the beginning, but that's not the only thing!" + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]", + explain: "That's correct!", + correct: true + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2", + explain: "A [CLS] special token is needed at the beginning as well as a [SEP] special token to separate the two sentences, but that's not all!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. What are the benefits of the `Dataset.map()` method? + + + +### 5. What does dynamic padding mean? + + + +### 6. What is the purpose of a collate function? + +DataCollatorWithPadding specifically." + }, + { + text: "It puts together all the samples in a batch.", + explain: "Correct! 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: "It preprocesses the whole dataset.", + explain: "That would be a preprocessing function, not a collate function." + }, + { + text: "It truncates the sequences in the dataset.", + explain: "A collate function is involved in handling individual batches, not the whole dataset. If you're interested in truncating, you can use the truncate argument of tokenizer." + } + ]} +/> + +### 7. What happens when you instantiate one of the `AutoModelForXxx` classes with a pretrained language model (such as `bert-base-uncased`) that corresponds to a different task than the one for which it was trained? + +AutoModelForSequenceClassification with bert-base-uncased, we got warnings when instantiating the model. The pretrained head is not used for the sequence classification task, so it's discarded and a new head is instantiated with random weights.", + correct: true + }, + { + text: "The head of the pretrained model is discarded.", + explain: "Something else needs to happen. Try again!" + }, + { + text: "Nothing, since the model can still be fine-tuned for the different task.", + explain: "The head of the pretrained model was not trained to solve this task, so we should discard the head!" + } + ]} +/> + +### 8. What's the purpose of `TrainingArguments`? + +Trainer.", + explain: "Correct!", + correct: true + }, + { + text: "It specifies the size of the model.", + explain: "The model size is defined by the model configuration, not the class TrainingArguments." + }, + { + text: "It just contains the hyperparameters used for evaluation.", + explain: "In the example, we specified where the model and its checkpoints will be saved. Try again!" + }, + { + text: "It just contains the hyperparameters used for training.", + explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. Try again!" + } + ]} +/> + +### 9. Why should you use the 🤗 Accelerate library? + +Trainer, not the 🤗 Accelerate library. Try again!" + }, + { + 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 + }, + { + text: "It provides more optimization functions.", + explain: "No, the 🤗 Accelerate library does not provide any optimization functions." + } + ]} +/> + +{:else} +### 4. What happens when you instantiate one of the `TFAutoModelForXxx` classes with a pretrained language model (such as `bert-base-uncased`) that corresponds to a different task than the one for which it was trained? + +TFAutoModelForSequenceClassification with bert-base-uncased, we got warnings when instantiating the model. The pretrained head is not used for the sequence classification task, so it's discarded and a new head is instantiated with random weights.", + correct: true + }, + { + text: "The head of the pretrained model is discarded.", + explain: "Something else needs to happen. Try again!" + }, + { + text: "Nothing, since the model can still be fine-tuned for the different task.", + explain: "The head of the pretrained model was not trained to solve this task, so we should discard the head!" + } + ]} +/> + +### 5. The TensorFlow models from `transformers` are already Keras models. What benefit does this offer? + +TPUStrategy scope, including the initialization of the model." + }, + { + 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 + }, + { + text: "You get to learn Keras as well as transformers.", + explain: "Correct, but we're looking for something else :)", + correct: true + }, + { + text: "You can easily compute metrics related to the dataset.", + explain: "Keras helps us with training and evaluating the model, not computing dataset-related metrics." + } + ]} +/> + +### 6. How can you define your own custom metric? + +tf.keras.metrics.Metric.", + explain: "Great!", + correct: true + }, + { + text: "Using the Keras functional API.", + explain: "Try again!" + }, + { + text: "By using a callable with signature metric_fn(y_true, y_pred).", + explain: "Correct!", + correct: true + }, + { + text: "By Googling it.", + explain: "That's not the answer we're looking for, but it should help you find it.", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file From f38ca66c13074f325e241c133dd3e803b55b6675 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sat, 30 Apr 2022 08:48:35 +0200 Subject: [PATCH 104/789] file removed --- chapters/fa/chapter3/2.mdx | 390 ---------------------------------- chapters/fa/chapter3/3.mdx | 172 --------------- chapters/fa/chapter3/3_tf.mdx | 203 ------------------ 3 files changed, 765 deletions(-) delete mode 100644 chapters/fa/chapter3/2.mdx delete mode 100644 chapters/fa/chapter3/3.mdx delete mode 100644 chapters/fa/chapter3/3_tf.mdx diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx deleted file mode 100644 index 5be25d104..000000000 --- a/chapters/fa/chapter3/2.mdx +++ /dev/null @@ -1,390 +0,0 @@ - - -# پردازش داده -# Processing the data - -{#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)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: - -```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)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه آموزش دادن روی آن آسان است. - -### بارگذاری یک داده از هاب - -### Loading a dataset from the Hub - -{#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` با استفاده از شماره‌گذاری, مانند یک 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 .'} -``` - -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `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)} -``` - -در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *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` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی، جمله اول و کدام بخش جمله دوم است. - - - -✏️ **امتحان کنید!** عنصر شماره ۱۵ داده را بردارید و دو جمله را به صورت جداگانه و جفت توکنایز کنید. تفاوت دو نتیجه چیست؟ - - - -اگر آیدی‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: - -```py -tokenizer.convert_ids_to_tokens(inputs["input_ids"]) -``` - -خواهیم داشت: - -we will get: - -```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, -) -``` -این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [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` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. - -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه‌داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. - -در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه‌داده به یکباره اعمال می‌کنیم. ما از `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 - }) -}) -``` - -شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. - - -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. - -آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. - - -### هم‌طول‌سازی پویا - -### Dynamic padding - - - -{#if fw === 'pt'} - -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. - -{:else} - -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. - -{/if} - -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `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} diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx deleted file mode 100644 index 749cfd511..000000000 --- a/chapters/fa/chapter3/3.mdx +++ /dev/null @@ -1,172 +0,0 @@ - - -# Fine-tuning a model with the Trainer API - - - - - -🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). - -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: - -```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 - -The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments("test-trainer") -``` - - - -💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) - - - -The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels: - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. - -Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `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, -) -``` - -Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! - -To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`: - -```py -trainer.train() -``` - -This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: - -1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). -2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). - - -### Evaluation - -Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: - -```py -predictions = trainer.predict(tokenized_datasets["validation"]) -print(predictions.predictions.shape, predictions.label_ids.shape) -``` - -```python out -(408, 2) (408,) -``` - -The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. - -As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: - -```py -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: - -```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} -``` - -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. - -Wrapping everything together, we get our `compute_metrics()` function: - -```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) -``` - -And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: - -```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, -) -``` - -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: - -``` -trainer.train() -``` - -This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. - -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. - - - -✏️ **Try it out!** Fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. - - - diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx deleted file mode 100644 index 85a4626dd..000000000 --- a/chapters/fa/chapter3/3_tf.mdx +++ /dev/null @@ -1,203 +0,0 @@ - - -# باز‌تنظیم یک مدل با استفاده از کِراس - -# Fine-tuning a model with Keras - - - -وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا آموزشِ مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU را انجام نداده‌اید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. - -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/). - -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: - -```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 models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. - - - -That means that once we have our data, very little work is required to begin training on it. - - - -As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: - -```py -from transformers import TFAutoModelForSequenceClassification - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. - -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. - - - -Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. - - - -```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, -) -``` - - - -Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. - - - - -### Improving training performance - - - -If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause -is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes -that optimizer with default values for all parameters, including learning rate. From long experience, though, we know -that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written -as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. - -In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate -over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* -the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is -`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial -value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, -though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. - -```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) -``` - - - -The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. - - - -Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new 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"]) -``` - -Now, we fit again: - -```py -model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -``` - - - -💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) - - - -### Model predictions - - - - -Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. - -```py -preds = model.predict(tf_validation_dataset)["logits"] -``` - -We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: - -```py -class_preds = np.argmax(preds, axis=1) -print(preds.shape, class_preds.shape) -``` - -```python out -(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: - -```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} -``` - -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. From bf5bf0f4c43b5374bded2ef20296d425f51214e2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sat, 30 Apr 2022 08:51:38 +0200 Subject: [PATCH 105/789] mistake fixed --- chapters/fa/chapter3/2.mdx | 390 +++++++++++++++++++++++++++++++++++++ chapters/fa/chapter3/4.mdx | 359 ---------------------------------- chapters/fa/chapter3/5.mdx | 20 -- chapters/fa/chapter3/6.mdx | 296 ---------------------------- 4 files changed, 390 insertions(+), 675 deletions(-) create mode 100644 chapters/fa/chapter3/2.mdx delete mode 100644 chapters/fa/chapter3/4.mdx delete mode 100644 chapters/fa/chapter3/5.mdx delete mode 100644 chapters/fa/chapter3/6.mdx diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx new file mode 100644 index 000000000..5be25d104 --- /dev/null +++ b/chapters/fa/chapter3/2.mdx @@ -0,0 +1,390 @@ + + +# پردازش داده +# Processing the data + +{#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)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: + +```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)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه آموزش دادن روی آن آسان است. + +### بارگذاری یک داده از هاب + +### Loading a dataset from the Hub + +{#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` با استفاده از شماره‌گذاری, مانند یک 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 .'} +``` + +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `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)} +``` + +در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *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` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی، جمله اول و کدام بخش جمله دوم است. + + + +✏️ **امتحان کنید!** عنصر شماره ۱۵ داده را بردارید و دو جمله را به صورت جداگانه و جفت توکنایز کنید. تفاوت دو نتیجه چیست؟ + + + +اگر آیدی‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +خواهیم داشت: + +we will get: + +```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, +) +``` +این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [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` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. + +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه‌داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. + +در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه‌داده به یکباره اعمال می‌کنیم. ما از `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 + }) +}) +``` + +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. + + +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. + +آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. + + +### هم‌طول‌سازی پویا + +### Dynamic padding + + + +{#if fw === 'pt'} + +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + +{:else} + +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + +{/if} + +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `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} diff --git a/chapters/fa/chapter3/4.mdx b/chapters/fa/chapter3/4.mdx deleted file mode 100644 index 54563ea7c..000000000 --- a/chapters/fa/chapter3/4.mdx +++ /dev/null @@ -1,359 +0,0 @@ -# A full training - - - - - -Now we'll see how to achieve the same results as we did in the last section without using the `Trainer` class. Again, we assume you have done the data processing in section 2. Here is a short summary covering everything you will need: - -```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) -``` - -### Prepare for training - -Before actually writing our training loop, we will need to define a few objects. The first ones are the dataloaders we will use to iterate over batches. But before we can define those dataloaders, we need to apply a bit of postprocessing to our `tokenized_datasets`, to take care of some things that the `Trainer` did for us automatically. Specifically, we need to: - -- Remove the columns corresponding to values the model does not expect (like the `sentence1` and `sentence2` columns). -- Rename the column `label` to `labels` (because the model expects the argument to be named `labels`). -- Set the format of the datasets so they return PyTorch tensors instead of lists. - -Our `tokenized_datasets` has one method for each of those steps: - -```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 -``` - -We can then check that the result only has columns that our model will accept: - -```python -["attention_mask", "input_ids", "labels", "token_type_ids"] -``` - -Now that this is done, we can easily define our 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 -) -``` - -To quickly check there is no mistake in the data processing, we can inspect a batch like this: - -```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])} -``` - -Note that the actual shapes will probably be slightly different for you since we set `shuffle=True` for the training dataloader and we are padding to the maximum length inside the batch. - -Now that we're completely finished with data preprocessing (a satisfying yet elusive goal for any ML practitioner), let's turn to the model. We instantiate it exactly as we did in the previous section: - -```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]) -``` - -All 🤗 Transformers models will return the loss when `labels` are provided, and we also get the logits (two for each input in our batch, so a tensor of size 8 x 2). - -We're almost ready to write our training loop! We're just missing two things: an optimizer and a learning rate scheduler. Since we are trying to replicate what the `Trainer` was doing by hand, we will use the same defaults. The optimizer used by the `Trainer` is `AdamW`, which is the same as Adam, but with a twist for weight decay regularization (see ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) by Ilya Loshchilov and Frank Hutter): - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -Finally, the learning rate scheduler used by default is just a linear decay from the maximum value (5e-5) to 0. To properly define it, we need to know the number of training steps we will take, which is the number of epochs we want to run multiplied by the number of training batches (which is the length of our training dataloader). The `Trainer` uses three epochs by default, so we will follow that: - -```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 -``` - -### The training loop - -One last thing: we will want to use the GPU if we have access to one (on a CPU, training might take several hours instead of a couple of minutes). To do this, we define a `device` we will put our model and our batches on: - -```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') -``` - -We are now ready to train! To get some sense of when training will be finished, we add a progress bar over our number of training steps, using the `tqdm` library: - -```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) -``` - -You can see that the core of the training loop looks a lot like the one in the introduction. We didn't ask for any reporting, so this training loop will not tell us anything about how the model fares. We need to add an evaluation loop for that. - - -### 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: - -```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} -``` - -Again, your results will be slightly different because of the randomness in the model head initialization and the data shuffling, but they should be in the same ballpark. - - - -✏️ **Try it out!** Modify the previous training loop to fine-tune your model on the SST-2 dataset. - - - -### Supercharge your training loop with 🤗 Accelerate - - - -The training loop we defined earlier works fine on a single CPU or GPU. But using the [🤗 Accelerate](https://github.com/huggingface/accelerate) library, with just a few adjustments we can enable distributed training on multiple GPUs or TPUs. Starting from the creation of the training and validation dataloaders, here is what our manual training loop looks like: - -```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) -``` - -And here are the changes: - -```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) -``` - -The first line to add is the import line. The second line instantiates an `Accelerator` object that will look at the environment and initialize the proper distributed setup. 🤗 Accelerate handles the device placement for you, so you can remove the lines that put the model on the device (or, if you prefer, change them to use `accelerator.device` instead of `device`). - -Then the main bulk of the work is done in the line that sends the dataloaders, the model, and the optimizer to `accelerator.prepare()`. This will wrap those objects in the proper container to make sure your distributed training works as intended. The remaining changes to make are removing the line that puts the batch on the `device` (again, if you want to keep this you can just change it to use `accelerator.device`) and replacing `loss.backward()` with `accelerator.backward(loss)`. - - -⚠️ In order to benefit from the speed-up offered by Cloud TPUs, we recommend padding your samples to a fixed length with the `padding="max_length"` and `max_length` arguments of the tokenizer. - - -If you'd like to copy and paste it to play around, here's what the complete training loop looks like with 🤗 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) -``` - -Putting this in a `train.py` script will make that script runnable on any kind of distributed setup. To try it out in your distributed setup, run the command: - -```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 -``` - -which will launch the distributed training. - -If you want to try this in a Notebook (for instance, to test it with TPUs on Colab), just paste the code in a `training_function()` and run a last cell with: - -```python -from accelerate import notebook_launcher - -notebook_launcher(training_function) -``` - -You can find more examples in the [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/fa/chapter3/5.mdx b/chapters/fa/chapter3/5.mdx deleted file mode 100644 index dda8cb7fe..000000000 --- a/chapters/fa/chapter3/5.mdx +++ /dev/null @@ -1,20 +0,0 @@ - - -# 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'} -* Learned about datasets in the [Hub](https://huggingface.co/datasets) -* Learned how to load and preprocess datasets, including using dynamic padding and collators -* Implemented your own fine-tuning and evaluation of a model -* Implemented a lower-level training loop -* Used 🤗 Accelerate to easily adapt your training loop so it works for multiple GPUs or TPUs - -{:else} -* Learned about datasets in the [Hub](https://huggingface.co/datasets) -* Learned how to load and preprocess datasets -* Learned how to fine-tune and evaluate a model with Keras -* Implemented a custom metric - -{/if} diff --git a/chapters/fa/chapter3/6.mdx b/chapters/fa/chapter3/6.mdx deleted file mode 100644 index 94d02da24..000000000 --- a/chapters/fa/chapter3/6.mdx +++ /dev/null @@ -1,296 +0,0 @@ - - - - -# 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? - - - -### 2. Search for the `ar_sarcasm` dataset in the [Hub](https://huggingface.co/datasets). Which task does it support? - -dataset card!" - }, - { - text: "Named entity recognition", - explain: "That's not it — take another look at the dataset card!" - }, - { - text: "Question answering", - explain: "Alas, this question was not answered correctly. Try again!" - } - ]} -/> - -### 3. How does the BERT model expect a pair of sentences to be processed? - -[SEP] special token is needed to separate the two sentences, but that's not the only thing!" - }, - { - text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2", - explain: "A [CLS] special token is required at the beginning, but that's not the only thing!" - }, - { - text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]", - explain: "That's correct!", - correct: true - }, - { - text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2", - explain: "A [CLS] special token is needed at the beginning as well as a [SEP] special token to separate the two sentences, but that's not all!" - } - ]} -/> - -{#if fw === 'pt'} -### 4. What are the benefits of the `Dataset.map()` method? - - - -### 5. What does dynamic padding mean? - - - -### 6. What is the purpose of a collate function? - -DataCollatorWithPadding specifically." - }, - { - text: "It puts together all the samples in a batch.", - explain: "Correct! 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: "It preprocesses the whole dataset.", - explain: "That would be a preprocessing function, not a collate function." - }, - { - text: "It truncates the sequences in the dataset.", - explain: "A collate function is involved in handling individual batches, not the whole dataset. If you're interested in truncating, you can use the truncate argument of tokenizer." - } - ]} -/> - -### 7. What happens when you instantiate one of the `AutoModelForXxx` classes with a pretrained language model (such as `bert-base-uncased`) that corresponds to a different task than the one for which it was trained? - -AutoModelForSequenceClassification with bert-base-uncased, we got warnings when instantiating the model. The pretrained head is not used for the sequence classification task, so it's discarded and a new head is instantiated with random weights.", - correct: true - }, - { - text: "The head of the pretrained model is discarded.", - explain: "Something else needs to happen. Try again!" - }, - { - text: "Nothing, since the model can still be fine-tuned for the different task.", - explain: "The head of the pretrained model was not trained to solve this task, so we should discard the head!" - } - ]} -/> - -### 8. What's the purpose of `TrainingArguments`? - -Trainer.", - explain: "Correct!", - correct: true - }, - { - text: "It specifies the size of the model.", - explain: "The model size is defined by the model configuration, not the class TrainingArguments." - }, - { - text: "It just contains the hyperparameters used for evaluation.", - explain: "In the example, we specified where the model and its checkpoints will be saved. Try again!" - }, - { - text: "It just contains the hyperparameters used for training.", - explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. Try again!" - } - ]} -/> - -### 9. Why should you use the 🤗 Accelerate library? - -Trainer, not the 🤗 Accelerate library. Try again!" - }, - { - 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 - }, - { - text: "It provides more optimization functions.", - explain: "No, the 🤗 Accelerate library does not provide any optimization functions." - } - ]} -/> - -{:else} -### 4. What happens when you instantiate one of the `TFAutoModelForXxx` classes with a pretrained language model (such as `bert-base-uncased`) that corresponds to a different task than the one for which it was trained? - -TFAutoModelForSequenceClassification with bert-base-uncased, we got warnings when instantiating the model. The pretrained head is not used for the sequence classification task, so it's discarded and a new head is instantiated with random weights.", - correct: true - }, - { - text: "The head of the pretrained model is discarded.", - explain: "Something else needs to happen. Try again!" - }, - { - text: "Nothing, since the model can still be fine-tuned for the different task.", - explain: "The head of the pretrained model was not trained to solve this task, so we should discard the head!" - } - ]} -/> - -### 5. The TensorFlow models from `transformers` are already Keras models. What benefit does this offer? - -TPUStrategy scope, including the initialization of the model." - }, - { - 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 - }, - { - text: "You get to learn Keras as well as transformers.", - explain: "Correct, but we're looking for something else :)", - correct: true - }, - { - text: "You can easily compute metrics related to the dataset.", - explain: "Keras helps us with training and evaluating the model, not computing dataset-related metrics." - } - ]} -/> - -### 6. How can you define your own custom metric? - -tf.keras.metrics.Metric.", - explain: "Great!", - correct: true - }, - { - text: "Using the Keras functional API.", - explain: "Try again!" - }, - { - text: "By using a callable with signature metric_fn(y_true, y_pred).", - explain: "Correct!", - correct: true - }, - { - text: "By Googling it.", - explain: "That's not the answer we're looking for, but it should help you find it.", - correct: true - } - ]} -/> - -{/if} \ No newline at end of file From 2847e0c39fd9e8973e77d030eba210393052ecf2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 1 May 2022 07:51:39 +0200 Subject: [PATCH 106/789] new file added --- chapters/fa/chapter3/3_tf.mdx | 205 ++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 chapters/fa/chapter3/3_tf.mdx diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx new file mode 100644 index 000000000..a6d0f70bb --- /dev/null +++ b/chapters/fa/chapter3/3_tf.mdx @@ -0,0 +1,205 @@ + + +# باز‌تنظیم یک مدل با استفاده از کِراس + +# Fine-tuning a model with Keras + + + +وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا آموزشِ مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU ندارید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. + +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/). + +نمونه کدِ‌های زیر فرض می‌کنند شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه جهت یادآوری آنچه نیاز دارید: + +The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: + +```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 models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. + + + +That means that once we have our data, very little work is required to begin training on it. + + + +As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. + +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. + + + +Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. + + + +```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, +) +``` + + + +Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. + + + + +### Improving training performance + + + +If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause +is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes +that optimizer with default values for all parameters, including learning rate. From long experience, though, we know +that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written +as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. + +In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate +over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* +the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is +`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial +value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, +though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. + +```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) +``` + + + +The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. + + + +Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new 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"]) +``` + +Now, we fit again: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) + + + +### Model predictions + + + + +Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(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: + +```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} +``` + +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. From 86af017ce8e3a97a2499ce87ac167e3024c2edc8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 1 May 2022 08:10:39 +0200 Subject: [PATCH 107/789] multiple new paragraph added to the translation --- chapters/fa/chapter3/3_tf.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index a6d0f70bb..bd943fd2e 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -54,16 +54,24 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` +### آموزش + ### Training +مدل‌های تِنسورفلو که از ترَنسفورمِرهای 🤗 وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. + TensorFlow models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. +آن به این معنی است که به محض اینکه در اختیار داشته باشیم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. + That means that once we have our data, very little work is required to begin training on it. +مانند [previous chapter](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب استفاده خواهیم کرد: + As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: ```py From e01d74c356516b1a0c0b102e3b419a7114f16dcc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 1 May 2022 08:14:47 +0200 Subject: [PATCH 108/789] minor update --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index bd943fd2e..df60da40a 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -64,7 +64,7 @@ TensorFlow models imported from 🤗 Transformers are already Keras models. Here -آن به این معنی است که به محض اینکه در اختیار داشته باشیم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. +آن به این معنی است که به محض اینکه داده‌مان در اختیار بگیریم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. That means that once we have our data, very little work is required to begin training on it. From cfee90e23fe4264ec9288ec912aa739464794674 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Tue, 3 May 2022 13:33:49 +0800 Subject: [PATCH 109/789] Fixed some bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese --- chapters/zh-CN/_toctree.yml | 1 + chapters/{zh => zh-CN}/chapter0/1.mdx | 0 chapters/{zh => zh-CN}/chapter2/1.mdx | 40 +- chapters/{zh => zh-CN}/chapter2/2.mdx | 706 ++++++++++++------------- chapters/{zh => zh-CN}/chapter2/3.mdx | 525 +++++++++---------- chapters/{zh => zh-CN}/chapter2/4.mdx | 478 ++++++++--------- chapters/{zh => zh-CN}/chapter2/5.mdx | 710 +++++++++++++------------- chapters/{zh => zh-CN}/chapter2/6.mdx | 330 ++++++------ chapters/{zh => zh-CN}/chapter2/7.mdx | 54 +- chapters/{zh => zh-CN}/chapter2/8.mdx | 586 ++++++++++----------- 10 files changed, 1716 insertions(+), 1714 deletions(-) rename chapters/{zh => zh-CN}/chapter0/1.mdx (100%) rename chapters/{zh => zh-CN}/chapter2/1.mdx (99%) rename chapters/{zh => zh-CN}/chapter2/2.mdx (97%) rename chapters/{zh => zh-CN}/chapter2/3.mdx (96%) rename chapters/{zh => zh-CN}/chapter2/4.mdx (98%) rename chapters/{zh => zh-CN}/chapter2/5.mdx (96%) rename chapters/{zh => zh-CN}/chapter2/6.mdx (97%) rename chapters/{zh => zh-CN}/chapter2/7.mdx (96%) rename chapters/{zh => zh-CN}/chapter2/8.mdx (97%) diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 26a1bf6e9..ea5134bf3 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -26,6 +26,7 @@ - local: chapter1/10 title: 章末小测验 quiz: 1 + - title: 2. 使用 🤗 Transformers sections: - local: chapter2/1 diff --git a/chapters/zh/chapter0/1.mdx b/chapters/zh-CN/chapter0/1.mdx similarity index 100% rename from chapters/zh/chapter0/1.mdx rename to chapters/zh-CN/chapter0/1.mdx diff --git a/chapters/zh/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx similarity index 99% rename from chapters/zh/chapter2/1.mdx rename to chapters/zh-CN/chapter2/1.mdx index a5b7387b3..bf6b07157 100644 --- a/chapters/zh/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -1,21 +1,21 @@ -# 介绍 - -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 - -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch -**nn.Module** 或者 TensorFlow **tf.kears.Mode1**,可以像它们各自的机器学习(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.Mode1**,可以像它们各自的机器学习(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/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx similarity index 97% rename from chapters/zh/chapter2/2.mdx rename to chapters/zh-CN/chapter2/2.mdx index 7d1922158..876e6d767 100644 --- a/chapters/zh/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -1,353 +1,353 @@ - - -# 管道的内部 - -{#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 - -我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 - - - -✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! - - + + +# 管道的内部 + +{#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/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx similarity index 96% rename from chapters/zh/chapter2/3.mdx rename to chapters/zh-CN/chapter2/3.mdx index 8f3c703e2..3efe1efe1 100644 --- a/chapters/zh/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -1,262 +1,263 @@ - - -# 模型 - -{#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/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx similarity index 98% rename from chapters/zh/chapter2/4.mdx rename to chapters/zh-CN/chapter2/4.mdx index fb4819296..89ca03579 100644 --- a/chapters/zh/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/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx similarity index 96% rename from chapters/zh/chapter2/5.mdx rename to chapters/zh-CN/chapter2/5.mdx index 1b73568ed..f4a7175aa 100644 --- a/chapters/zh/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/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx similarity index 97% rename from chapters/zh/chapter2/6.mdx rename to chapters/zh-CN/chapter2/6.mdx index e4b5c6295..044bbb632 100644 --- a/chapters/zh/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/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx similarity index 96% rename from chapters/zh/chapter2/7.mdx rename to chapters/zh-CN/chapter2/7.mdx index 1835352f6..8c6e7156a 100644 --- a/chapters/zh/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,27 +1,27 @@ -# 基本用法完成! - -很好地完成了到这里的课程!总而言之,在本章中,您可以: - -- 学习了Transformers模型的基本构造块。 - - -- 了解了标记化管道的组成。 - - -- 了解了如何在实践中使用Transformers模型。 - - -- 学习了如何利用分词器将文本转换为模型可以理解的张量。 - - -- 将分词器和模型一起设置,以从文本到预测。 - - -- 了解了inputs IDs的局限性,并了解了attention mask。 - - -- 使用多功能和可配置的分词器方法。 - - - -从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 +# 基本用法完成! + +很好地完成了到这里的课程!总而言之,在本章中,您可以: + +- 学习了Transformers模型的基本构造块。 + + +- 了解了标记化管道的组成。 + + +- 了解了如何在实践中使用Transformers模型。 + + +- 学习了如何利用分词器将文本转换为模型可以理解的张量。 + + +- 将分词器和模型一起设置,以从文本到预测。 + + +- 了解了inputs IDs的局限性,并了解了attention mask。 + + +- 使用多功能和可配置的分词器方法。 + + + +从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 diff --git a/chapters/zh/chapter2/8.mdx b/chapters/zh-CN/chapter2/8.mdx similarity index 97% rename from chapters/zh/chapter2/8.mdx rename to chapters/zh-CN/chapter2/8.mdx index 89cc36502..27ecb0e14 100644 --- a/chapters/zh/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -1,293 +1,293 @@ - - - - -# 章末小测试 - -### 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} From e8cbf4a2cbec6a8576df46ccb2ee9e6eca5c2efd Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 3 May 2022 10:04:37 +0200 Subject: [PATCH 110/789] new paragraphs added. --- chapters/fa/chapter3/3_tf.mdx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index df60da40a..e85917338 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -64,13 +64,13 @@ TensorFlow models imported from 🤗 Transformers are already Keras models. Here -آن به این معنی است که به محض اینکه داده‌مان در اختیار بگیریم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. +آن به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. That means that once we have our data, very little work is required to begin training on it. -مانند [previous chapter](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب استفاده خواهیم کرد: +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب استفاده خواهیم کرد: As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: @@ -80,12 +80,18 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از نمونه‌سازیِ این مدل پیش‌تعلیم شما یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی جفت جمله‌ها پیش‌تعلیم نشده، بنابراین لایه-سر مدل پیش‌تعلیم حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌ای به جای آن اضافه شده. هشدارها نشان می‌دهند که برخی از وزنها استفاده نشده‌اند (آنهایی که مربوط به لایه-سر جدید هستند) و برخی دیگر به صورت تصادفی مقدار‌دهی شده‌ند. (آنهایی که مربوط به لایه-سر پیش‌تعلیم حذف شده هستند). در نتیجه این شما را تشویق به آموزش مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. + You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. +برای باز‌تنظیم مدل روی مجموعه‌داده مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند باز‌تنظیم را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر 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. +توجه داشته باشید که مدل‌های ترَنسفورمِر 🤗 قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این آموزش خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. + Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. From bc24213a642611042a3ed191d622201638eb3f33 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 4 May 2022 16:48:54 +0200 Subject: [PATCH 111/789] toc_tree updated --- chapters/fa/_toctree.yml | 10 ++++++++++ chapters/fa/chapter3/3_tf.mdx | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 179c9d87a..6a1b81a92 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -13,6 +13,16 @@ - local: chapter2/1 title: مقدمه +- title: 3. بازتنظیمِ یک مدل از پیش‌تعلیم دیده # Translate this! + sections: + - local: chapter3/1 + title: مقدمه # Translate this! + - local: chapter3/2 + title: پردازش داده # Translate this! + - local: chapter3/3 + title: باز‌تنظیم یک مدل با استفاده از Trainer-API یا کِراس # Translate this! + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - title: واژه‌نامه sections: - local: glossary/1 diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index e85917338..f13f71740 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -116,7 +116,6 @@ Note a very common pitfall here — you *can* just pass the name of the loss as
- ### Improving training performance From cca41c13ac6f7d2ed64c6555451c4b8d34e00055 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:10:49 +0200 Subject: [PATCH 112/789] rtl div added --- chapters/fa/chapter3/1.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 6cfa5d3ee..3869ae26f 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,3 +1,4 @@ +
# مقدمه @@ -20,4 +21,6 @@ {/if} -جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) \ No newline at end of file +جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) + +
\ No newline at end of file From 7d0e787d13a71761dc79b3149eb8dd9332a9df96 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:12:22 +0200 Subject: [PATCH 113/789] rtl div added --- chapters/fa/chapter3/2.mdx | 3 +++ chapters/fa/chapter3/3_tf.mdx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5be25d104..f8d35c49a 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,3 +1,5 @@ +
+ # پردازش داده @@ -388,3 +390,4 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( این هم از این! حالا می‌توانیم این مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} +
\ No newline at end of file diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index f13f71740..77df31ef0 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -1,3 +1,4 @@ +
# باز‌تنظیم یک مدل با استفاده از کِراس @@ -216,3 +217,5 @@ 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. + +
\ No newline at end of file From b94b45719caa3013d5f5cb30ee124ec24cea4119 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:29:05 +0200 Subject: [PATCH 114/789] Applied reviews to chapters part 1 and 2. New phrases added to G --- chapters/fa/chapter3/1.mdx | 10 +++++----- chapters/fa/chapter3/2.mdx | 32 ++++++++++++++++---------------- chapters/fa/chapter3/3_tf.mdx | 2 +- chapters/fa/glossary/1.mdx | 2 ++ 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 3869ae26f..ce767c60b 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -3,18 +3,18 @@ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل پیش‌تعلیم را خودتان بازتنظیم کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان بازتنظیم کنید؟ {#if fw === 'pt'} -* چگونه یک مجموعه‌داده بزرگ را از هاب تهیه کنید -* چگونه از API سطح بالای "Trainer" برای بازتنظیم مدل استفاده کنید +* چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید +* چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید * چگونه یک چرخه‌تعلیم دلخواه درست کنید -* چگونه از کتابخانه Accelerate 🤗 برای اجرای چرخه‌تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید +* چگونه از کتابخانه `Accelerate` 🤗 برای اجرای چرخه‌تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید {:else} -* چگونه یک مجموعه‌داده بزرگ را از هاب تهیه کنید +* چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید * چگونه از کِراس برای بازتنظیم مدل استفاده کنید * چگونه از کِراس برای استخراج پیش بینی‌ها استفاده کنید * چگونه از یک متریک دلخواه استفاده کنید diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f8d35c49a..e9b393752 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -138,7 +138,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` مجموعه‌داده‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` دیتاسِت‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. ```py raw_train_dataset.features @@ -157,7 +157,7 @@ raw_train_dataset.features ✏️ **امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟
-### پیش‌پردازشِ یک مجموعه‌داده +### پیش‌پردازشِ یک دیتاسِت {#if fw === 'pt'} @@ -165,7 +165,7 @@ raw_train_dataset.features {/if} -به منظور پیش‌پردازش مجموعه‌داده، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش‌پردازش دیتاسِت، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: ```py from transformers import AutoTokenizer @@ -230,7 +230,7 @@ we will get: در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکنایز شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل مجموعه‌داده‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ مجموعه‌داده‌ی training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل دیتاسِتمان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دیتاسِت training اینگونه می‌باشد: ```py tokenized_dataset = tokenizer( @@ -240,26 +240,26 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل مجموعه‌داده در حین توکنایز کردن داشته باشید (در حالی که مجموعه‌داده‌های موجود در پایگاه مجموعه‌داده‌‌ی 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه می‌دارید). +این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت 🤗 فایل‌هایی از نوع [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.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` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع یک دیکشنری (مثل اقلام داخل دیتاسِت) دریافت میکند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids` بازمی‌گرداند. توجه داشته باشید از آنجایی که توکنایزر روی لیستهایی از جفت جمله‌ها کار می‌کند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر مجموعه‌داده. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل مجموعه‌داده به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از مجموعه‌داده ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -روشی که کتابخانه مجموعه‌داده 🤗 این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به مجموعه‌داده‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، +روشی که کتابخانه دیتاسِت 🤗 این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، ```python out DatasetDict({ @@ -281,7 +281,7 @@ DatasetDict({ شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های مجموعه‌داده افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در مجموعه‌داده که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. @@ -302,7 +302,7 @@ DatasetDict({ {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های مجموعه‌داده‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} ```py @@ -330,7 +330,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] ``` -تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل مجموعه‌داده باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: ```py batch = data_collator(samples) @@ -361,13 +361,13 @@ batch = data_collator(samples) -✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی مجموعه‌داده GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور مجموعه‌داده‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک مجموعه‌داده 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با مجموعه‌داده‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -387,7 +387,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! حالا می‌توانیم این مجموعه‌داده‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! حالا می‌توانیم این دیتاسِت‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} \ No newline at end of file diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 77df31ef0..0db366978 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -85,7 +85,7 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای باز‌تنظیم مدل روی مجموعه‌داده مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند باز‌تنظیم را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر epoch. +برای کوک‌ کردن مدل روی دیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر 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. diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 8587b671b..81979861d 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -54,6 +54,8 @@ | Script | اسکریپت‌ | | Feature | قابلیت | | Folder | پوشه | +|fine-tune|کوک کردن| +|dataset|دیتاسِت| معادل‌هایی که استفاده نمی‌کنیم: From d764628ee96bcf9eca1a5428aeba396407a2ab5b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:30:28 +0200 Subject: [PATCH 115/789] test remove rtl div --- chapters/fa/chapter3/1.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index ce767c60b..340b8de5b 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,9 +1,9 @@ -
+ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان بازتنظیم کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کردن کنید؟ {#if fw === 'pt'} @@ -15,7 +15,7 @@ {:else} * چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید -* چگونه از کِراس برای بازتنظیم مدل استفاده کنید +* چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید * چگونه از کِراس برای استخراج پیش بینی‌ها استفاده کنید * چگونه از یک متریک دلخواه استفاده کنید @@ -23,4 +23,3 @@ جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) -
\ No newline at end of file From 59a6274313947db36af6ec314d64be128a56c4aa Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:31:27 +0200 Subject: [PATCH 116/789] test remove rtl div --- chapters/fa/chapter3/1.mdx | 3 ++- chapters/fa/chapter3/2.mdx | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 340b8de5b..55406ebd7 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,4 +1,4 @@ - +
# مقدمه @@ -23,3 +23,4 @@ جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در 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 index e9b393752..614c54dd9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,4 +1,4 @@ -
+ @@ -355,7 +355,7 @@ batch = data_collator(samples) 'labels': torch.Size([8])} ``` -به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل مان می‌تواند با آنها کار کند، آماده هستیم برای انجام بازتنظیم مدل: +به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل مان می‌تواند با آنها کار کند، آماده هستیم برای انجام کوک‌ کردن مدل: {/if} @@ -390,4 +390,3 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( این هم از این! حالا می‌توانیم این دیتاسِت‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} -
\ No newline at end of file From 0349df6e50521bb1d8fb57aff8b41945cee655b2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:42:30 +0200 Subject: [PATCH 117/789] comment applies --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 614c54dd9..844654825 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -124,7 +124,7 @@ DatasetDict({ این دستور مجموعه‌داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. -ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از شماره‌گذاری, مانند یک dictionary دسترسی پیدا کنیم: +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک dictionary دسترسی پیدا کنیم: ```py raw_train_dataset = raw_datasets["train"] @@ -138,7 +138,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسبها از پیش مقادیر صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم تابع `features` دیتاسِت‌مان `raw_train_dataset`‌ را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. ```py raw_train_dataset.features From 8bdfb66d9c19b3974c8e79b9aa325563ed94a3f0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 15:55:36 +0200 Subject: [PATCH 118/789] all comment applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 844654825..efe4edc0e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -138,7 +138,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این تابع نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر سطون را به ما خواهد گفت. ```py raw_train_dataset.features From f651342d62f98414c7c15d11803a05369757772b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 19:46:18 +0200 Subject: [PATCH 119/789] comments applies --- chapters/fa/chapter3/1.mdx | 12 ++++++------ chapters/fa/chapter3/3_tf.mdx | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 55406ebd7..140a34482 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -3,24 +3,24 @@ # مقدمه -در [فصل ۲](/course/chapter2) نحوه استفاده از توکنایزرها و مدل‌های پیش‌تعلیم را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کردن کنید؟ +در [فصل ۲](/course/chapter2) نحوه استفاده از توکِنایزرها و مدل‌های از پیش تعلیم دیده را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کنید؟ {#if fw === 'pt'} * چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید * چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید -* چگونه یک چرخه‌تعلیم دلخواه درست کنید -* چگونه از کتابخانه `Accelerate` 🤗 برای اجرای چرخه‌تعلیم دلخواه در هر تنظیمات غیر متمرکزی استفاده کنید +* چگونه یک چرخه‌ تعلیم دلخواه درست کنید +* چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات غیر متمرکزی استفاده کنید {:else} * چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید * چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید -* چگونه از کِراس برای استخراج پیش بینی‌ها استفاده کنید -* چگونه از یک متریک دلخواه استفاده کنید +* چگونه از کِراس برای استخراج پیش‌بینی‌ها استفاده کنید +* چگونه از یک مِتریک دلخواه استفاده کنید {/if} -جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) +جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فِیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 0db366978..5adede6a0 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -59,7 +59,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ### Training -مدل‌های تِنسورفلو که از ترَنسفورمِرهای 🤗 وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. +مدل‌های تِنسورفلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. TensorFlow models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. @@ -91,7 +91,7 @@ To fine-tune the model on our dataset, we just have to `compile()` our model and -توجه داشته باشید که مدل‌های ترَنسفورمِر 🤗 قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این آموزش خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این آموزش خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. From 1c9dbd52b1c7e7e2eeb1567b6bb3d63d5fb951d0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 20:01:32 +0200 Subject: [PATCH 120/789] some comments applied --- chapters/fa/chapter3/1.mdx | 8 ++++---- chapters/fa/glossary/1.mdx | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 140a34482..e3c8ac49d 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -7,17 +7,17 @@ {#if fw === 'pt'} -* چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید * چگونه یک چرخه‌ تعلیم دلخواه درست کنید -* چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات غیر متمرکزی استفاده کنید +* چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات توزیع شده‌ای استفاده کنید {:else} -* چگونه یک دیتاسِت بزرگ را از هاب تهیه کنید +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید * چگونه از کِراس برای استخراج پیش‌بینی‌ها استفاده کنید -* چگونه از یک مِتریک دلخواه استفاده کنید +* چگونه از مِتریک دلخواه استفاده کنید {/if} diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 81979861d..a5ba8475e 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -40,6 +40,7 @@ | Windows | ویندوز | | macOS | سیستم‌عامل مک | | Distribution | توزیع | +| Distributed | توزیع شده | | Linux | لینوکس | | Workload | محاسبه، محاسبات | | Package Manager | پکیج‌منیجر | From 7bb86f1f52d6287ae3b3be4b1e58d2b714323116 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 20:18:24 +0200 Subject: [PATCH 121/789] comments applied --- chapters/fa/chapter3/1.mdx | 6 +++--- chapters/fa/chapter3/2.mdx | 32 ++++++++++++++++---------------- chapters/fa/chapter3/3_tf.mdx | 2 +- chapters/fa/glossary/1.mdx | 6 +++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index e3c8ac49d..6b3879845 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -7,20 +7,20 @@ {#if fw === 'pt'} -* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه دِیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید * چگونه یک چرخه‌ تعلیم دلخواه درست کنید * چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات توزیع شده‌ای استفاده کنید {:else} -* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه دِیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید * چگونه از کِراس برای استخراج پیش‌بینی‌ها استفاده کنید * چگونه از مِتریک دلخواه استفاده کنید {/if} -جهت بارگذاری چکپوینت تعلیم دیده خود در هاب هاگینگ‌فِیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) +جهت آپلود نقطه تعلیم خود در هاب هاگینگ‌فِیس، احتیاج به یک حساب کاربری در 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 index efe4edc0e..3fc081e0e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -157,7 +157,7 @@ raw_train_dataset.features ✏️ **امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ -### پیش‌پردازشِ یک دیتاسِت +### پیش‌پردازشِ یک دِیتاسِت‌ {#if fw === 'pt'} @@ -165,7 +165,7 @@ raw_train_dataset.features {/if} -به منظور پیش‌پردازش دیتاسِت، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: ```py from transformers import AutoTokenizer @@ -230,7 +230,7 @@ we will get: در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکنایز شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل دیتاسِتمان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دیتاسِت training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل دِیتاسِتمان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت training اینگونه می‌باشد: ```py tokenized_dataset = tokenizer( @@ -240,26 +240,26 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت 🤗 فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه می‌دارید). +این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دِیتاسِت در حین توکنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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.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` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [🤗 Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع یک دیکشنری (مثل اقلام داخل دِیتاسِت) دریافت میکند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask`، و `token_type_ids` بازمی‌گرداند. توجه داشته باشید از آنجایی که توکنایزر روی لیستهایی از جفت جمله‌ها کار می‌کند، همانطور که قبلا مشاهده کردیم، این تابع نیز درصورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `()map` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دِیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -روشی که کتابخانه دیتاسِت 🤗 این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، +روشی که کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، ```python out DatasetDict({ @@ -278,10 +278,10 @@ DatasetDict({ }) ``` -شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر 🤗 از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های دِیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دِیتاسِت که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. @@ -302,7 +302,7 @@ DatasetDict({ {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای 🤗 چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دِیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} ```py @@ -330,7 +330,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] ``` -تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دِیتاسِت باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: ```py batch = data_collator(samples) @@ -361,13 +361,13 @@ batch = data_collator(samples) -✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دِیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت 🤗 را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -387,6 +387,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! حالا می‌توانیم این دیتاسِت‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 5adede6a0..ca62b27ca 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -85,7 +85,7 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر epoch. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر 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. diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index a5ba8475e..e8642a8bc 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -55,9 +55,9 @@ | Script | اسکریپت‌ | | Feature | قابلیت | | Folder | پوشه | -|fine-tune|کوک کردن| -|dataset|دیتاسِت| - +| Fine-tune | کوک کردن | +| Dataset | دِیتاسِت | +| Upload | آپلود | معادل‌هایی که استفاده نمی‌کنیم: From 6551eb89262ba181baae56b0c2b250725808b1da Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 21:01:21 +0200 Subject: [PATCH 122/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 9 ++++----- chapters/fa/glossary/1.mdx | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3fc081e0e..0e656dc9b 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -2,15 +2,14 @@ -# پردازش داده -# Processing the data +# پردازش داده {#if fw === 'pt'} @@ -19,7 +18,7 @@ @@ -27,7 +26,7 @@ {#if fw === 'pt'} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: +در ادامه مثال [فصل قبل](/course/chapter2)،نشان می‌دهیم چگونه مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ در PyTorch آموزش می‌دهیم: ```python import torch diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index e8642a8bc..b6ef6e20a 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -58,6 +58,8 @@ | Fine-tune | کوک کردن | | Dataset | دِیتاسِت | | Upload | آپلود | +| Batch | بَتچ | + معادل‌هایی که استفاده نمی‌کنیم: From e9664af0de71095986f5b828255b4c8deb93db40 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 21:09:13 +0200 Subject: [PATCH 123/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 0e656dc9b..ce08a8254 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -26,7 +26,7 @@ {#if fw === 'pt'} -در ادامه مثال [فصل قبل](/course/chapter2)،نشان می‌دهیم چگونه مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ در PyTorch آموزش می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط pytorch شرح می‌دهیم: ```python import torch @@ -52,7 +52,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش یک مدل ترتیبی در یک بَتچ توسط pytorch را شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط pytorch شرح می‌دهیم: ```python import tensorflow as tf From a4311f544a95904185a3b81908fefcf3cdd5930c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 21:09:53 +0200 Subject: [PATCH 124/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index ce08a8254..00b5e9807 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -26,7 +26,7 @@ {#if fw === 'pt'} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط pytorch شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط PyTorch شرح می‌دهیم: ```python import torch @@ -52,7 +52,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط pytorch شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط PyTorch شرح می‌دهیم: ```python import tensorflow as tf From 1474c43ecb28a9ccf682af0f2225d125bd3c3268 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 21:17:07 +0200 Subject: [PATCH 125/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 16 ++++++++-------- chapters/fa/chapter3/3_tf.mdx | 14 ++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 00b5e9807..e083ba8ff 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -26,7 +26,7 @@ {#if fw === 'pt'} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط PyTorch شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط پایتورچ شرح می‌دهیم: ```python import torch @@ -52,7 +52,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه آموزش مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط PyTorch شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط پایتورچ شرح می‌دهیم: ```python import tensorflow as tf @@ -77,9 +77,9 @@ model.train_on_batch(batch, labels) {/if} -البته آموزش با استفاده از دو جمله به نتایج چشمگیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه‌داده بزرگتر خواهید داشت. +البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه‌داده بزرگتر خواهید داشت. -در این بخش ما از مجموعه‌داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه آموزش دادن روی آن آسان است. +در این بخش ما از مجموعه‌داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه تعلیم روی آن آسان است. ### بارگذاری یک داده از هاب @@ -293,11 +293,11 @@ DatasetDict({ {#if fw === 'pt'} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {:else} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه آموزش را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال آموزش روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {/if} @@ -366,7 +366,7 @@ batch = data_collator(samples) {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای آموزش تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! +توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -386,6 +386,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که آموزش پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index ca62b27ca..9f0da034b 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -12,7 +12,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter3/section3_tf.ipynb"}, ]} /> -وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا آموزشِ مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU ندارید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. +وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU ندارید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. 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/). @@ -55,9 +55,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -### آموزش - -### Training +### تعلیم مدل‌های تِنسورفلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. @@ -65,7 +63,7 @@ TensorFlow models imported from 🤗 Transformers are already Keras models. Here -آن به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار خیلی کمی لازم است تا آموزش را روی آن شروع کنیم. +آن به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار خیلی کمی لازم است تا تعلیم را روی آن شروع کنیم. That means that once we have our data, very little work is required to begin training on it. @@ -81,17 +79,17 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از نمونه‌سازیِ این مدل پیش‌تعلیم شما یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی جفت جمله‌ها پیش‌تعلیم نشده، بنابراین لایه-سر مدل پیش‌تعلیم حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌ای به جای آن اضافه شده. هشدارها نشان می‌دهند که برخی از وزنها استفاده نشده‌اند (آنهایی که مربوط به لایه-سر جدید هستند) و برخی دیگر به صورت تصادفی مقدار‌دهی شده‌ند. (آنهایی که مربوط به لایه-سر پیش‌تعلیم حذف شده هستند). در نتیجه این شما را تشویق به آموزش مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از نمونه‌سازیِ این مدل پیش‌تعلیم شما یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی جفت جمله‌ها پیش‌تعلیم نشده، بنابراین لایه-سر مدل پیش‌تعلیم حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌ای به جای آن اضافه شده. هشدارها نشان می‌دهند که برخی از وزنها استفاده نشده‌اند (آنهایی که مربوط به لایه-سر جدید هستند) و برخی دیگر به صورت تصادفی مقدار‌دهی شده‌ند. (آنهایی که مربوط به لایه-سر پیش‌تعلیم حذف شده هستند). در نتیجه این شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای آموزش را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر epoch. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای تعلیم را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر 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. -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این آموزش خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. From 42c55ff31a9f4846ea25a5740df3a79579259832 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 21:52:02 +0200 Subject: [PATCH 126/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index e083ba8ff..54940b668 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -26,7 +26,7 @@ {#if fw === 'pt'} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط پایتورچ شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: ```python import torch @@ -52,7 +52,7 @@ optimizer.step() ``` {:else} -در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ توسط پایتورچ شرح می‌دهیم: +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از تِنسورفلو شرح می‌دهیم: ```python import tensorflow as tf @@ -77,23 +77,21 @@ model.train_on_batch(batch, labels) {/if} -البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای بدست آوردن نتایج بهتر نیاز به آماده سازی مجموعه‌داده بزرگتر خواهید داشت. +البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دِیتاسِت بزرگتری خواهید داشت. -در این بخش ما از مجموعه‌داده MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی William B. Dolan و Chris Brockett.، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این مجموعه‌داده این است که مجموعه‌داده کوچکیست و تجربه تعلیم روی آن آسان است. +در این بخش ما از دِیتاسِت MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این دِیتاسِت این است که دِیتاسِت کوچکیست و تجربه تعلیم روی آن آسان است. ### بارگذاری یک داده از هاب -### Loading a dataset from the Hub - {#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/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. +هاب تنها شامل مدلها نمی‌باشد؛ بلکه شامل دِیتاسِت‌های متعدد در بسیاری زبان‌های مختلف می‌باشد. شما می‌توانید دِیتاسِت‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک دِیتاسِت جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی داده MRPC تمرکز کنیم! این داده یکی از ۱۰ داده [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدلهای یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. -کتابخانه مجموعه‌داده 🤗 یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک مجموعه‌داده در هاب ارائه می‌کند. ما می‌توانیم مجموعه‌داده MRPC را به روش زیر دانلود کنیم: +کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: ```py from datasets import load_dataset @@ -121,7 +119,7 @@ DatasetDict({ همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test). - این دستور مجموعه‌داده را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دِیتاسِت را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک dictionary دسترسی پیدا کنیم: From 258b9828883c60df8d681dba89e4e9b81619605d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 22:37:06 +0200 Subject: [PATCH 127/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 18 +++++++++--------- chapters/fa/glossary/1.mdx | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 54940b668..d33ba0c76 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -79,9 +79,9 @@ model.train_on_batch(batch, labels) البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دِیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دِیتاسِت MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این داده شامل ۵،۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشاندهنده متناظر بودن جملات (به عنوان مثال، آیا دو جمله معنی یکسانی دارند یا خیر) می‌باشد. علت انتخاب این دِیتاسِت این است که دِیتاسِت کوچکیست و تجربه تعلیم روی آن آسان است. +در این بخش ما از دِیتاسِت MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دِیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دِیتاسِت این است که دِیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. -### بارگذاری یک داده از هاب +### بارگذاری دِیتاسِت‌ها از هاب {#if fw === 'pt'} @@ -89,9 +89,9 @@ 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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی دِیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دِیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. -کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: +کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: ```py from datasets import load_dataset @@ -117,11 +117,11 @@ DatasetDict({ }) ``` -همانطور که می بینید یک شیء "DatasetDict" بدست می‌آوریم که شامل مجموعه training، مجموعه validation، و مجموعه test می‌باشد. هریک از اینها شامل چندین سطون (`label`، `sentence2`، `sentence1`، و `idx`) و تعداد متغیری ردیف داده که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳،۶۶۸ جفت جمله در مجموعه training وجود دارد، ۴۰۸ تا در مجموعه validation، و ۱،۷۲۵ تا در مجموعه test). +همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دِیتاسِت را دانلود و به صورت پیش فرض در زیرشاخه‌ی *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازیتان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي
*~/.cache/huggingface/dataset*
ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. -ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک dictionary دسترسی پیدا کنیم: +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک `dictionary` دسترسی پیدا کنیم: ```py raw_train_dataset = raw_datasets["train"] @@ -135,7 +135,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر سطون را به ما خواهد گفت. +می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. ```py raw_train_dataset.features @@ -315,7 +315,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" ``` {/if} -اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا سطون‌های `idx`، `sentence1`، و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: +اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1`، و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: ```py samples = tokenized_datasets["train"][:8] diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index b6ef6e20a..4950f5558 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -59,7 +59,7 @@ | Dataset | دِیتاسِت | | Upload | آپلود | | Batch | بَتچ | - +| Label | برچسب | معادل‌هایی که استفاده نمی‌کنیم: From c93fe7e7e006d11c781b1f17512b67acec5c77d8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 22:51:29 +0200 Subject: [PATCH 128/789] new fixes applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d33ba0c76..5511c7c7f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -119,7 +119,7 @@ DatasetDict({ همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي
*~/.cache/huggingface/dataset*
ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک `dictionary` دسترسی پیدا کنیم: From e08e7cd52399475e786b003040225a2789a37f31 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 5 May 2022 23:01:32 +0200 Subject: [PATCH 129/789] Finally, all fixes applied --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 5511c7c7f..eb0df5e0f 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -119,9 +119,9 @@ DatasetDict({ همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید زیرشاخه‌ی ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ي ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. -ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک `dictionary` دسترسی پیدا کنیم: +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک دیکشنری دسترسی پیدا کنیم: ```py raw_train_dataset = raw_datasets["train"] From 46f1fca796690d27fb130e5d6ecb312792840017 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:14:00 +0200 Subject: [PATCH 130/789] additional fixes made following the comments in the review --- chapters/fa/chapter3/2.mdx | 64 +++++++++++++++++++------------------- chapters/fa/glossary/1.mdx | 3 ++ 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index eb0df5e0f..9dde9a11e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -119,7 +119,7 @@ DatasetDict({ همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دِیتاسِت را دانلود و به صورت پیش فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ي ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دِیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ي ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک دیکشنری دسترسی پیدا کنیم: @@ -135,7 +135,7 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای اینکه بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. ```py raw_train_dataset.features @@ -148,13 +148,13 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` -در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent`، و `1` مربوط به `equivalent` می‌باشد،. +در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. -✏️ **امتحان کنید!** عنصر شماره ۱۵ از داده training و عنصر شماره ۸۷ از داده validation را مشاهده کنید. برچسبهای آنها چیست؟ +✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` و عنصر شماره ۸۷ از مجموعه `validation` را مشاهده کنید. برچسب‌های آنها چیست؟ -### پیش‌پردازشِ یک دِیتاسِت‌ +### پیش‌پردازشِ دِیتاسِت‌‌ها {#if fw === 'pt'} @@ -162,7 +162,7 @@ raw_train_dataset.features {/if} -به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنایزر بدهیم، در نتیجه می‌توانیم مستقیما تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکنایز کنیم: +به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: ```py from transformers import AutoTokenizer @@ -173,7 +173,7 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` -با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده سازی کند: +با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند که متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکِنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده‌سازی کند: ```py inputs = tokenizer("This is the first sentence.", "This is the second one.") @@ -188,15 +188,15 @@ inputs } ``` -در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی، جمله اول و کدام بخش جمله دوم است. +در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. -✏️ **امتحان کنید!** عنصر شماره ۱۵ داده را بردارید و دو جمله را به صورت جداگانه و جفت توکنایز کنید. تفاوت دو نتیجه چیست؟ +✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` را بردارید و دو جمله را به صورت جداگانه و جفت توکِنایز کنید. تفاوت دو نتیجه چیست؟ -اگر آیدی‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: +اگر شناسه‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) @@ -210,24 +210,24 @@ we will get: ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [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` می‌باشد. +همانطور که می‌بینید، بخش‌هایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند اندیسِ نشان دهنده نوع توکِن آنها `0` و بخش‌هایی که مربوط به `sentence2 [SEP]` هستند اندیس نشان دهنده نوع توکِن‌شان `1` می‌باشد. -توجه داشته باشید که اگر چکپوینت متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. +توجه داشته باشید که اگر نقطه تعلیمِ متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. -در اینجا، مدل BERT با آیدی‌هایی که نشاندهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبانِ ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ برعهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با آیدی‌هایی که نشان دهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبانِ ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. - در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالتها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. + در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالت‌ها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. -در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکنایز شده خود باشید: مادامی که از چکپوینت یکسان برای توکنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکنایزر می‌داند چه چیزی برای مدل فراهم کند. +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِنایز شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکنایز کردن کل دِیتاسِتمان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم نوکنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های padding و truncation که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت training اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دِیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت `training` اینگونه می‌باشد: ```py tokenized_dataset = tokenizer( @@ -237,26 +237,26 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -این روش به خوبی کار می‌کند، اما مشکلش این است که یک dictionary برمیگرداند (از کلیدهای ما شامل، `input_ids`, `attention_mask`, و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند). همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دِیتاسِت در حین توکنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه می‌دارید). +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دِیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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.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` استفاده کنیم که توکنایزر را به میزان زیادی سریعتر خواهد کرد. این توکنایزر با توکنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع یک دیکشنری (مثل اقلام داخل دِیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از جفت جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دِیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه جویی خواهد کرد. +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دِیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع توکنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریعتر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -روشی که کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را اعمال می‌کند، با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، می‌باشد:، +کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: ```python out DatasetDict({ @@ -275,10 +275,10 @@ DatasetDict({ }) ``` -شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `()map` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکنایز کردن سریعتر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکنایزر سریع که با این کتابخانه پشتیبانی میشود استفاده نمی‌کنید، این روش میتواند پیش‌پردازش شما را سریعتر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask`، و `token_type_ids`، برمیگرداند به گونه‌ای که این سه فیلد به همه بخش‌های دِیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دِیتاسِت که تابع `()map` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دِیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دِیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. @@ -291,15 +291,15 @@ DatasetDict({ {#if fw === 'pt'} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش فرض تابعی است که نمونه‌های شما را به ماتریس PyTorch تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPU ها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش‌ فرض تابعی است که نمونه‌های شما را به ماتریس پایتورچ تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان‌پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {:else} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. collator پیش فرض تابعی است که فقط نمونه‌های شما را به tf.Tensor تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل، یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیشگیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPU اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. `collator` پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {/if} -برای انجام اینکار در عمل، ما باید یک تابع collate تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دِیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند، و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دِیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهند. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} ```py @@ -315,7 +315,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" ``` {/if} -اجازه دهید چند نمونه از مجموعه training مان، که می‌خواهیم باهم بَتچ کنیم، برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1`، و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: +اجازه دهید چند نمونه از مجموعه `training`مان را که می‌خواهیم باهم بَتچ کنیم برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1` و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: ```py samples = tokenized_datasets["train"][:8] @@ -327,7 +327,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] ``` -تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دِیتاسِت باید به اندازه بزرگترین طول یا بزرگترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دِیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: ```py batch = data_collator(samples) @@ -352,7 +352,7 @@ batch = data_collator(samples) 'labels': torch.Size([8])} ``` -به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل مان می‌تواند با آنها کار کند، آماده هستیم برای انجام کوک‌ کردن مدل: +به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده هستیم برای کوک‌ کردن مدل: {/if} @@ -364,7 +364,7 @@ batch = data_collator(samples) {#if fw === 'tf'} -توجه داشته باشید که ما مجموعه‌‌داده مان و یک collator داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را collate کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه میکند: `()to_tf_dataset`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع collation دلخواه. `tf.data.Dataset` یک فرمت بومی تنسورفلو است که کِراس می‌تواند برای `()model.fit` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! +توجه داشته باشید که ما دِیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع `collation` دلخواه. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 4950f5558..bde5aae82 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -60,6 +60,9 @@ | Upload | آپلود | | Batch | بَتچ | | Label | برچسب | +| Padding | هم‌طول‌سازی | +| Dynamic padding | هم‌طول‌سازی پویا | + معادل‌هایی که استفاده نمی‌کنیم: From cb33b96fce6d091a09386b709a2aa92b72852c68 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:24:30 +0200 Subject: [PATCH 131/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 66 +++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 9dde9a11e..f68698102 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,5 +1,4 @@ - - +
# پردازش داده @@ -28,6 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: +
```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,10 +50,13 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` +
+ {:else} در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از تِنسورفلو شرح می‌دهیم: +
```python import tensorflow as tf import numpy as np @@ -74,6 +77,7 @@ model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` +
{/if} @@ -93,13 +97,16 @@ model.train_on_batch(batch, labels) کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: +
```py from datasets import load_dataset raw_datasets = load_dataset("glue", "mrpc") raw_datasets ``` +
+
```python out DatasetDict({ train: Dataset({ @@ -116,6 +123,7 @@ DatasetDict({ }) }) ``` +
همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). @@ -123,30 +131,38 @@ DatasetDict({ ما می‌توانیم به هر جفت از جملات در شئ `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`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +
```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)} ``` +
در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. @@ -164,6 +180,7 @@ raw_train_dataset.features به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: +
```py from transformers import AutoTokenizer @@ -172,14 +189,18 @@ 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], @@ -187,6 +208,7 @@ inputs '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` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. @@ -198,24 +220,30 @@ inputs اگر شناسه‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: +
```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` +
خواهیم داشت: we will get: +
```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` می‌باشد. @@ -229,6 +257,7 @@ we will get: اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دِیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت `training` اینگونه می‌باشد: +
```py tokenized_dataset = tokenizer( raw_datasets["train"]["sentence1"], @@ -237,14 +266,18 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` +
+ این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دِیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. @@ -252,12 +285,16 @@ def tokenize_function(example): در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: +
```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` +
+ کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: +
```python out DatasetDict({ train: Dataset({ @@ -274,6 +311,7 @@ DatasetDict({ }) }) ``` +
شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. @@ -285,8 +323,6 @@ DatasetDict({ ### هم‌طول‌سازی پویا -### Dynamic padding - {#if fw === 'pt'} @@ -302,55 +338,73 @@ DatasetDict({ برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دِیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `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])} ``` +
به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده هستیم برای کوک‌ کردن مدل: @@ -366,6 +420,7 @@ batch = data_collator(samples) توجه داشته باشید که ما دِیتاسِت‌مان و یک `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"], @@ -383,7 +438,10 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( batch_size=8, ) ``` +
این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} + +
\ No newline at end of file From cf3fcc2b7ee9c0cb6dd09a6722dbfd8413d1b3f8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:27:24 +0200 Subject: [PATCH 132/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f68698102..f0e93879e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,7 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: -
+ ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,7 +50,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` -
+<\span> {:else} From 67bf22ad6c4007914b9b12e06e502f185c821cc2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:28:03 +0200 Subject: [PATCH 133/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index f0e93879e..6bbdc795a 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,7 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: - + ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,7 +50,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` -<\span> + {:else} From 52a06715f01a771a810ba548adaf24640e8d2db2 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:31:48 +0200 Subject: [PATCH 134/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 6bbdc795a..bca43a80b 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,6 +1,6 @@ -
+
# پردازش داده {#if fw === 'pt'} @@ -27,7 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: - +
```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,7 +50,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` - +
{:else} From 59313b6f8bda471206c96e9e38660a29dd995d72 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:35:25 +0200 Subject: [PATCH 135/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 88 ++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bca43a80b..af3e07f89 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,7 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: -
+ ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,13 +50,12 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` -
{:else} در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از تِنسورفلو شرح می‌دهیم: -
+ ```python import tensorflow as tf import numpy as np @@ -77,7 +76,6 @@ model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` -
{/if} @@ -97,16 +95,14 @@ model.train_on_batch(batch, labels) کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: -
+ ```py from datasets import load_dataset raw_datasets = load_dataset("glue", "mrpc") raw_datasets ``` -
-
```python out DatasetDict({ train: Dataset({ @@ -123,7 +119,6 @@ DatasetDict({ }) }) ``` -
همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). @@ -131,38 +126,34 @@ DatasetDict({ ما می‌توانیم به هر جفت از جملات در شئ `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`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. -
+ ```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)} ``` -
+ در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. @@ -180,7 +171,7 @@ raw_train_dataset.features به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: -
+ ```py from transformers import AutoTokenizer @@ -189,18 +180,15 @@ 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], @@ -208,7 +196,7 @@ inputs '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` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. @@ -220,30 +208,29 @@ inputs اگر شناسه‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: -
+ ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` -
+ خواهیم داشت: we will get: -
+ ```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` می‌باشد. @@ -257,7 +244,7 @@ we will get: اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دِیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت `training` اینگونه می‌باشد: -
+ ```py tokenized_dataset = tokenizer( raw_datasets["train"]["sentence1"], @@ -266,18 +253,18 @@ tokenized_dataset = tokenizer( truncation=True, ) ``` -
+ این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دِیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. @@ -285,16 +272,16 @@ def tokenize_function(example): در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: -
+ ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -
+ کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: -
+ ```python out DatasetDict({ train: Dataset({ @@ -311,7 +298,6 @@ DatasetDict({ }) }) ``` -
شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. @@ -339,72 +325,70 @@ DatasetDict({ {#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])} ``` -
+ به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده هستیم برای کوک‌ کردن مدل: @@ -420,7 +404,7 @@ batch = data_collator(samples) توجه داشته باشید که ما دِیتاسِت‌مان و یک `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"], @@ -438,7 +422,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( batch_size=8, ) ``` -
+ این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. From 2a3502c398ab1f2b84f2f6f91627e84f50be62f8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:37:48 +0200 Subject: [PATCH 136/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index af3e07f89..bfacbb3f9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,7 +27,7 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: - +
```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,6 +50,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` +<\div> {:else} From 39696feab64cef2a3ae5e80c23e7bdbee31e137e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:39:02 +0200 Subject: [PATCH 137/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index bfacbb3f9..23d71cb34 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -27,7 +27,24 @@ در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: +
+ +```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 import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -50,7 +67,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` -<\div> + {:else} From 62dedb1bf74f52650263ca63a5a995ef54ded25b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:39:39 +0200 Subject: [PATCH 138/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 23d71cb34..0fc1ae1fa 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -30,21 +30,6 @@
-```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 import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification @@ -68,6 +53,7 @@ loss.backward() optimizer.step() ``` +<\div> {:else} From f588f85c9843c2ecc10d213f2b9266a3ba12c9e8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:40:51 +0200 Subject: [PATCH 139/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 0fc1ae1fa..3b80ceb7e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -53,12 +53,13 @@ loss.backward() optimizer.step() ``` -<\div> +
{:else} در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از تِنسورفلو شرح می‌دهیم: +
```python import tensorflow as tf @@ -81,6 +82,8 @@ labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` +
+ {/if} البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دِیتاسِت بزرگتری خواهید داشت. From de26443cfb5c2f6200706294a29c962c191be720 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 00:45:54 +0200 Subject: [PATCH 140/789] lrt and rtl divisions added --- chapters/fa/chapter3/2.mdx | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3b80ceb7e..2d97fbcdf 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -102,6 +102,7 @@ model.train_on_batch(batch, labels) کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: +
```py from datasets import load_dataset @@ -110,6 +111,10 @@ raw_datasets = load_dataset("glue", "mrpc") raw_datasets ``` +
+ +
+ ```python out DatasetDict({ train: Dataset({ @@ -127,18 +132,25 @@ DatasetDict({ }) ``` +
+ همانطور که می بینید یک شیء `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, @@ -146,14 +158,20 @@ raw_train_dataset[0] 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` +
می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +
```py raw_train_dataset.features ``` +
+ +
+ ```python out {'sentence1': Value(dtype='string', id=None), 'sentence2': Value(dtype='string', id=None), @@ -161,6 +179,7 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` +
در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. @@ -178,6 +197,7 @@ raw_train_dataset.features به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: +
```py from transformers import AutoTokenizer @@ -188,14 +208,21 @@ 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], @@ -204,6 +231,7 @@ inputs } ``` +
در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. @@ -215,29 +243,37 @@ inputs اگر شناسه‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: +
```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` +
خواهیم داشت: we will get: +
+ ```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` می‌باشد. @@ -251,6 +287,7 @@ we will get: اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دِیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت `training` اینگونه می‌باشد: +
```py tokenized_dataset = tokenizer( @@ -261,17 +298,20 @@ tokenized_dataset = tokenizer( ) ``` +
این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دِیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. @@ -279,15 +319,18 @@ def tokenize_function(example): در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: +
```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` +
کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: +
```python out DatasetDict({ @@ -306,6 +349,8 @@ DatasetDict({ }) ``` +
+ شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. @@ -332,6 +377,7 @@ DatasetDict({ {#if fw === 'pt'} +
```py from transformers import DataCollatorWithPadding @@ -339,9 +385,11 @@ from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` +
{:else} +
```py from transformers import DataCollatorWithPadding @@ -349,11 +397,13 @@ from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") ``` +
{/if} اجازه دهید چند نمونه از مجموعه `training`مان را که می‌خواهیم باهم بَتچ کنیم برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1` و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: +
```py samples = tokenized_datasets["train"][:8] @@ -361,22 +411,30 @@ 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 [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]), @@ -385,9 +443,11 @@ batch = data_collator(samples) 'labels': TensorShape([8])} ``` +
{:else} +
```python out {'attention_mask': torch.Size([8, 67]), @@ -396,6 +456,7 @@ batch = data_collator(samples) 'labels': torch.Size([8])} ``` +
به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده هستیم برای کوک‌ کردن مدل: @@ -411,6 +472,7 @@ batch = data_collator(samples) توجه داشته باشید که ما دِیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع `collation` دلخواه. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! +
```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -430,6 +492,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` +
این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. From 5753b150e81f824edf863d53cbc8baf21cc05ad7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 6 May 2022 13:20:34 +0200 Subject: [PATCH 141/789] remove unfinished translation from merge --- chapters/fa/chapter3/3_tf.mdx | 219 ---------------------------------- 1 file changed, 219 deletions(-) delete mode 100644 chapters/fa/chapter3/3_tf.mdx diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx deleted file mode 100644 index 9f0da034b..000000000 --- a/chapters/fa/chapter3/3_tf.mdx +++ /dev/null @@ -1,219 +0,0 @@ -
- - -# باز‌تنظیم یک مدل با استفاده از کِراس - -# Fine-tuning a model with Keras - - - -وقتی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر تنظیمات GPU ندارید، می‌توانید به GPUها یا TPUها مجانی روی [Google Colab](https://colab.research.google.com/) دسترسی داشته باشید. - -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/). - -نمونه کدِ‌های زیر فرض می‌کنند شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه جهت یادآوری آنچه نیاز دارید: - -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: - -```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 models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. - - - -آن به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار خیلی کمی لازم است تا تعلیم را روی آن شروع کنیم. - -That means that once we have our data, very little work is required to begin training on it. - - - -مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب استفاده خواهیم کرد: - -As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: - -```py -from transformers import TFAutoModelForSequenceClassification - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از نمونه‌سازیِ این مدل پیش‌تعلیم شما یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی جفت جمله‌ها پیش‌تعلیم نشده، بنابراین لایه-سر مدل پیش‌تعلیم حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌ای به جای آن اضافه شده. هشدارها نشان می‌دهند که برخی از وزنها استفاده نشده‌اند (آنهایی که مربوط به لایه-سر جدید هستند) و برخی دیگر به صورت تصادفی مقدار‌دهی شده‌ند. (آنهایی که مربوط به لایه-سر پیش‌تعلیم حذف شده هستند). در نتیجه این شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. - -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. - -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای تعلیم را گزارش دهد، بعلاوه‌ی خطای تایید در انتهای هر 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. - - - -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های Keras ندارند - آنها به صورت خودکار یک تابع خطای مناسب استفاده می‌کنند که آنرا به صورت داخلی محاسبه می‌کنند. اگر آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت که برچسب‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب مجزا، که روش معمول استفاده از برچسب‌ها در مدل‌های Keras می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای گول‌زننده باشد. به هر حال، برای دسته‌بندی رشته‌‌ای، یک تابع خطای استانداد Keras به خوبی کار می‌کند، بنابراین آن چیزی است که ما در اینجا استفاده خواهیم کرد. - -Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. - - - -```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, -) -``` - - - -Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. - - - -### Improving training performance - - - -If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause -is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes -that optimizer with default values for all parameters, including learning rate. From long experience, though, we know -that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written -as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. - -In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate -over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* -the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is -`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial -value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, -though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. - -```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) -``` - - - -The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. - - - -Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new 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"]) -``` - -Now, we fit again: - -```py -model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -``` - - - -💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) - - - -### Model predictions - - - - -Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. - -```py -preds = model.predict(tf_validation_dataset)["logits"] -``` - -We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: - -```py -class_preds = np.argmax(preds, axis=1) -print(preds.shape, class_preds.shape) -``` - -```python out -(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: - -```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} -``` - -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. - -
\ No newline at end of file From a2705ee6f2441f32b52dd7579500c34479217dae Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Sat, 7 May 2022 01:54:16 +0800 Subject: [PATCH 142/789] Add files via upload Formatting revisions and some translation corrections --- chapters/zh-CN/chapter2/1.mdx | 37 +- chapters/zh-CN/chapter2/2.mdx | 709 ++++++++++++++++----------------- chapters/zh-CN/chapter2/3.mdx | 527 ++++++++++++------------- 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 | 54 +-- chapters/zh-CN/chapter2/8.mdx | 586 ++++++++++++++-------------- 8 files changed, 1716 insertions(+), 1715 deletions(-) diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index bf6b07157..a24c162da 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -1,21 +1,18 @@ -# 介绍 - -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 - -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch -**nn.Module** 或者 TensorFlow **tf.kears.Mode1**,可以像它们各自的机器学习(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 876e6d767..0e4afa233 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -1,353 +1,356 @@ - - -# 管道的内部 - -{#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 - -我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 - - - -✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! - - + + +# 管道的内部 + +{#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-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 3efe1efe1..31341f166 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -1,263 +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 89ca03579..fb4819296 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 f4a7175aa..1b73568ed 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 044bbb632..e4b5c6295 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 8c6e7156a..1835352f6 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,27 +1,27 @@ -# 基本用法完成! - -很好地完成了到这里的课程!总而言之,在本章中,您可以: - -- 学习了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 27ecb0e14..89cc36502 100644 --- a/chapters/zh-CN/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -1,293 +1,293 @@ - - - - -# 章末小测试 - -### 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} From 515782148b0a8b5d0d43835200fbbb130edff92c Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Sat, 7 May 2022 02:33:33 +0800 Subject: [PATCH 143/789] run make style to format chapter1 session3 From 7c3c3f5320824d45fef96f1fd6b847a6ba57ce6d Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Sat, 7 May 2022 02:37:39 +0800 Subject: [PATCH 144/789] run make style to format code From 737c180a3d31a48c2759dc9677b85f812067307f Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Sat, 7 May 2022 02:43:43 +0800 Subject: [PATCH 145/789] run make style to format code --- chapters/de/chapter3/3_tf.mdx | 3 +-- chapters/en/chapter1/3.mdx | 4 +--- chapters/en/chapter2/2.mdx | 5 +---- chapters/en/chapter3/3_tf.mdx | 3 +-- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +--- chapters/en/chapter7/2.mdx | 17 ++++------------- chapters/en/chapter7/4.mdx | 5 +---- chapters/en/chapter7/5.mdx | 3 +-- chapters/en/chapter7/7.mdx | 5 +---- chapters/es/chapter1/3.mdx | 4 +--- chapters/fa/chapter2/2.mdx | 5 +---- chapters/fr/chapter3/3_tf.mdx | 3 +-- chapters/fr/chapter5/4.mdx | 2 +- chapters/fr/chapter6/8.mdx | 4 +--- chapters/fr/chapter7/2.mdx | 17 ++++------------- chapters/fr/chapter7/4.mdx | 5 +---- chapters/fr/chapter7/5.mdx | 3 +-- chapters/fr/chapter7/7.mdx | 5 +---- chapters/it/chapter1/3.mdx | 4 +--- chapters/ko/chapter1/3.mdx | 4 +--- chapters/pt/chapter2/2.mdx | 5 +---- chapters/ru/chapter1/3.mdx | 4 +--- chapters/th/chapter1/3.mdx | 4 +--- chapters/th/chapter2/2.mdx | 5 +---- chapters/zh-CN/chapter1/3.mdx | 4 +--- 26 files changed, 32 insertions(+), 97 deletions(-) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index dd1be7835..566457a0e 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..cd6aee466 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..313c1fc53 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 3c72b30fb..1ed74d777 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..b7d2609f7 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..c7cef7308 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,9 +404,7 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index b6592184c..c2409c107 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -414,9 +414,7 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -664,9 +662,7 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -775,10 +771,7 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -789,9 +782,7 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index cc9d7ff0d..03d0ebf45 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -796,10 +796,7 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 18ee26280..68cc54e54 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -929,8 +929,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index e0aa33268..c82e2d62d 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1030,10 +1030,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..04ac7f60a 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 534a03758..0c87dceec 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,10 +43,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index d9cc961f4..4a720568b 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 8657e3b62..5b5969ddd 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -90,7 +90,7 @@ Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, q ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index cdb445291..35f9d2ed1 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -405,9 +405,7 @@ Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenize from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 7fb7fae81..af1b86891 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -413,9 +413,7 @@ Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTok from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -663,9 +661,7 @@ Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenC from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -774,10 +770,7 @@ D'abord nous devons construire le `DataLoader`s à partir de nos jeux de donnée from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -788,9 +781,7 @@ Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne cont ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 48d83a6da..ab6c3687a 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -793,10 +793,7 @@ 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, + 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 diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 0ba896f6d..9d0daa3c8 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -941,8 +941,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e301b1bac..a8c4735ef 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1048,10 +1048,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..3690bcae5 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..3359ab062 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..b689c074d 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..008db7af8 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..a72f16354 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,9 +151,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..24718bd2d 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..1e7e91108 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,9 +132,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` ```python out From 42b81a4fec56db0d1579485b1b67325c3267b0c0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 8 May 2022 22:26:43 +0200 Subject: [PATCH 146/789] pulled G from upstream. --- chapters/fa/glossary/1.mdx | 63 +++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index d2a216617..fe42e4885 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -54,7 +54,6 @@ | Windows | ویندوز | | macOS | سیستم‌عامل مک | | Distribution | توزیع | -| Distributed | توزیع شده | | Linux | لینوکس | | Workload | محاسبه، محاسبات | | Package Manager | پکیج‌منیجر | @@ -74,6 +73,68 @@ | Self-Contained | خودکفا | | Script | اسکریپت‌ | | 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 | قطعه کد | +| Widget | ویجت | +| Hidden State | وضعیت پنهان | +| Feature, as in model | فیچر | +| High-Dimensional | بُعد بالا | +| Multi-Dimensional | چند بُعدی | +| Vector, as in Python | وِکتور | +| Sequence | رشته | +| Index, as in an array or list | اندیس | +| Project, as in math | پروجکت؟ | +| Embedding | embedding? | +| Tokenized | توکِن‌شده | +| Mask Filling | پر کردن جاهای خالی متن | +| Attention Mechanism | مکانیزم توجه | +| 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 | لوجیت؟ | +| SoftMax | سافت‌مکس | +| Loss Function | تابع هزینه | +| Activation Layer | لایه فعال‌سازی | +| Cross Entropy | آنتروپی متقابل | +| Head, as in model | سَر | +| Weight, as in model | وزن | +| Weights, as in model | وزن‌ها | +| Set, as for variable | تخصیص مقدار | +| Environment Variable | متغیر محیطی | +| Metadata | متادیتا | +| Encode, not as in cypher | کد شده؟، کد گذاری؟ | +| Cache | انبار کردن | معادل‌هایی که استفاده نمی‌کنیم: From db01f41ee41f14b45cf6bcb8677f3b67e7ef96f0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 08:19:05 +0200 Subject: [PATCH 147/789] some comments applied --- chapters/fa/_toctree.yml | 4 +-- chapters/fa/chapter3/1.mdx | 7 +++--- chapters/fa/chapter3/2.mdx | 50 ++++++++++++++++++++------------------ 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 454df675c..f785fe42c 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -24,14 +24,12 @@ - local: chapter4/2 title: بکارگیری مدل‌های از پیش تعلیم دیده -- title: 3. بازتنظیمِ یک مدل از پیش‌تعلیم دیده # Translate this! +- title: 3. کوک کردن یک مدل از پیش تعلیم دیده # Translate this! sections: - local: chapter3/1 title: مقدمه # Translate this! - local: chapter3/2 title: پردازش داده # Translate this! - - local: chapter3/3 - title: باز‌تنظیم یک مدل با استفاده از Trainer-API یا کِراس # Translate this! local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - title: واژه‌نامه diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index 6b3879845..b3f520423 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -1,20 +1,21 @@ -
+
+ # مقدمه در [فصل ۲](/course/chapter2) نحوه استفاده از توکِنایزرها و مدل‌های از پیش تعلیم دیده را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کنید؟ {#if fw === 'pt'} -* چگونه دِیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید * چگونه یک چرخه‌ تعلیم دلخواه درست کنید * چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات توزیع شده‌ای استفاده کنید {:else} -* چگونه دِیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید * چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید * چگونه از کِراس برای استخراج پیش‌بینی‌ها استفاده کنید * چگونه از مِتریک دلخواه استفاده کنید diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 2d97fbcdf..d0fec0593 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -86,11 +86,11 @@ model.train_on_batch(batch, labels) {/if} -البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دِیتاسِت بزرگتری خواهید داشت. +البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دِیتاسِت MRPC (Microsoft Research Paraphrase Corpus) که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دِیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دِیتاسِت این است که دِیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. +در این بخش ما از دیتاسِت MRPC[1^] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. -### بارگذاری دِیتاسِت‌ها از هاب +### بارگذاری دیتاسِت‌ها از هاب {#if fw === 'pt'} @@ -98,9 +98,9 @@ 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_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی دیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. -کتابخانه دِیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دِیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دِیتاسِت MRPC را به روش زیر دانلود کنیم: +کتابخانه دیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دیتاسِت MRPC را به روش زیر دانلود کنیم:
@@ -136,9 +136,9 @@ DatasetDict({ همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دِیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ي *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ي ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. -ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس گذاری, مانند یک دیکشنری دسترسی پیدا کنیم: +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس, مانند یک دیکشنری دسترسی پیدا کنیم:
@@ -160,7 +160,7 @@ raw_train_dataset[0]
-می‌بینم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت.
@@ -181,13 +181,13 @@ raw_train_dataset.features
-در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ي *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. +در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. ✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` و عنصر شماره ۸۷ از مجموعه `validation` را مشاهده کنید. برچسب‌های آنها چیست؟ -### پیش‌پردازشِ دِیتاسِت‌‌ها +### پیش‌پردازشِ دیتاسِت‌‌ها {#if fw === 'pt'} @@ -195,7 +195,7 @@ raw_train_dataset.features {/if} -به منظور پیش‌پردازش دِیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: +به منظور پیش‌پردازش دیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم:
@@ -285,7 +285,7 @@ we will get: در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِنایز شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دِیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دِیتاسِت `training` اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دیتاسِت `training` اینگونه می‌باشد:
@@ -300,9 +300,9 @@ tokenized_dataset = tokenizer(
-این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دِیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دِیتاسِت‌های موجود در پایگاه دِیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌می‌دارید). +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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.html#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیش‌تری علاوه‌بر توکِنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِنایز کند:
@@ -313,11 +313,11 @@ def tokenize_function(example):
-این تابع یک دیکشنری (مثل اقلام داخل دِیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از جفت جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع یک دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از جفت جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دِیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. +توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دِیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دِیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد:
@@ -328,7 +328,7 @@ tokenized_datasets
-کتابخانه دِیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دِیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: +کتابخانه دیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند:
@@ -354,7 +354,7 @@ DatasetDict({ شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دِیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دِیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. @@ -373,7 +373,7 @@ DatasetDict({ {/if} -برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دِیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهند. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهند. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} @@ -421,7 +421,7 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se
-تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دِیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند:
@@ -464,13 +464,13 @@ batch = data_collator(samples) -✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دِیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. {#if fw === 'tf'} -توجه داشته باشید که ما دِیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور دِیتاسِت‌تان می‌پیچد، با یک تابع `collation` دلخواه. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دِیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دِیتاسِت‌مان مشاهده کنیم! +توجه داشته باشید که ما دیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور دیتاسِت‌تان می‌پیچد، با یک تابع `collation` دلخواه. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم!
@@ -494,8 +494,10 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
-این هم از این! حالا می‌توانیم این دِیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. +این هم از این! حالا می‌توانیم این دیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. {/if} +[^1]: Microsoft Research Paraphrase Corpus +
\ No newline at end of file From 888da94645609a100afc212947367022ffa69c22 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 08:23:39 +0200 Subject: [PATCH 148/789] some comments applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d0fec0593..3b1102fe1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -88,7 +88,7 @@ model.train_on_batch(batch, labels) البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دیتاسِت MRPC[1^] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. +در این بخش ما از دیتاسِت MRPC[^1] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. ### بارگذاری دیتاسِت‌ها از هاب From f405cff611cb0b02a5ed73c0c553903e26664d63 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 08:24:42 +0200 Subject: [PATCH 149/789] some comments applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3b1102fe1..dd49018e0 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -88,7 +88,7 @@ model.train_on_batch(batch, labels) البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دیتاسِت MRPC[^1] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. +در این بخش ما از دیتاسِت [^1]MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. ### بارگذاری دیتاسِت‌ها از هاب From 7c0eee286262358c38540b4f51e7e270ed776a13 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 08:25:38 +0200 Subject: [PATCH 150/789] some comments applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index dd49018e0..d98c3646c 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -88,7 +88,7 @@ model.train_on_batch(batch, labels) البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دیتاسِت [^1]MRPC که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. +در این بخش ما از دیتاسِت MRPC [^1]که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. ### بارگذاری دیتاسِت‌ها از هاب From 5189d1aa03f4f58f93ec40b78e179e2147530a6f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 08:26:01 +0200 Subject: [PATCH 151/789] some comments applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d98c3646c..3b1102fe1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -88,7 +88,7 @@ model.train_on_batch(batch, labels) البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. -در این بخش ما از دیتاسِت MRPC [^1]که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. +در این بخش ما از دیتاسِت MRPC[^1] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. ### بارگذاری دیتاسِت‌ها از هاب From 73c5ddee04e059a89023c61c3a135912a22c469e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 9 May 2022 12:39:51 +0200 Subject: [PATCH 152/789] multiple comments applied --- chapters/fa/chapter3/2.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 3b1102fe1..69b7e6b3e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -160,7 +160,7 @@ raw_train_dataset[0]
-می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین مجبور نیستیم هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدارِ عددیِ صحیح به کدام برچسب مربوط می‌شود، می‌توانیم ویژگی‌های ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین لازم نیست هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم قابلیت‌های ‌`raw_train_dataset`‌ را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت.
@@ -181,13 +181,13 @@ raw_train_dataset.features
-در پشت صحنه، `برچسب` از نوع `برچسبِ کلاس` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. +در پشت صحنه، `label` از نوع `ClassLabel` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. ✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` و عنصر شماره ۸۷ از مجموعه `validation` را مشاهده کنید. برچسب‌های آنها چیست؟ -### پیش‌پردازشِ دیتاسِت‌‌ها +### پیش‌پردازش دیتاسِت‌‌ها {#if fw === 'pt'} @@ -275,17 +275,17 @@ we will get:
-همانطور که می‌بینید، بخش‌هایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند اندیسِ نشان دهنده نوع توکِن آنها `0` و بخش‌هایی که مربوط به `sentence2 [SEP]` هستند اندیس نشان دهنده نوع توکِن‌شان `1` می‌باشد. +همانطور که می‌بینید، بخش‌هایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند اندیس نشان دهنده نوع توکِن آنها `0` و بخش‌هایی که مربوط به `sentence2 [SEP]` هستند اندیس نشان دهنده نوع توکِن‌شان `1` می‌باشد. -توجه داشته باشید که اگر نقطه تعلیمِ متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. +توجه داشته باشید که اگر نقطه تعلیم متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. -در اینجا، مدل BERT با آیدی‌هایی که نشان دهنده نوعِ توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبانِ ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با آیدی‌هایی که نشان دهنده نوع توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبان ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالت‌ها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِنایز شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کلِ دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازشِ دیتاسِت `training` اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد:
@@ -300,7 +300,7 @@ tokenized_dataset = tokenizer(
-این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کلِ دیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌می‌دارید). +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌می‌دارید). به منظور نگهداشتن داده به صورت یک دیتاسِت، از تابع [`()Dataset.map`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیش‌تری علاوه‌بر توکِنایز کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِنایز کند: @@ -421,7 +421,7 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se
-تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طولِ قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طول قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند:
From 15fc23267eaa239fe83dba82ce425377ceb67977 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Thu, 12 May 2022 08:57:25 +0200 Subject: [PATCH 153/789] Fix style --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 3 +- chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/fr/chapter3/3_tf.mdx | 379 ++--- chapters/fr/chapter5/4.mdx | 2 +- chapters/fr/chapter6/8.mdx | 1130 +++++++-------- chapters/fr/chapter7/2.mdx | 1953 +++++++++++++------------- chapters/fr/chapter7/4.mdx | 1995 ++++++++++++++------------- chapters/fr/chapter7/5.mdx | 2129 ++++++++++++++-------------- chapters/fr/chapter7/7.mdx | 2453 +++++++++++++++++---------------- chapters/it/chapter1/3.mdx | 4 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 715 +++++----- 27 files changed, 5457 insertions(+), 5389 deletions(-) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 566457a0e..dd1be7835 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index cd6aee466..ac22e7e8f 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index 313c1fc53..d1304d737 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 1ed74d777..3c72b30fb 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index b7d2609f7..cb90067f4 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index c7cef7308..301648c7e 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,7 +404,9 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index c2409c107..b6592184c 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -414,7 +414,9 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -662,7 +664,9 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -771,7 +775,10 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -782,7 +789,9 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 03d0ebf45..cc9d7ff0d 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -796,7 +796,10 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 68cc54e54..18ee26280 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -929,7 +929,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index c82e2d62d..e0aa33268 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1030,7 +1030,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index 04ac7f60a..c725bb68d 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 0c87dceec..534a03758 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,7 +43,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 4a720568b..bc96a7d05 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -1,189 +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/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 5b5969ddd..8657e3b62 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -90,7 +90,7 @@ Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, q ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 35f9d2ed1..8c8af3b5b 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,564 +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, 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/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index af1b86891..110c5e1ab 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,972 +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 ab6c3687a..cfc6af14e 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,996 +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 9d0daa3c8..cfac55b89 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1064 +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 a8c4735ef..e2074354a 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1,1225 +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/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 3690bcae5..7fb506a94 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index 3359ab062..f32892430 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index b689c074d..88c9a068e 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 008db7af8..2f28dc98a 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index a72f16354..9ab990db5 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,7 +151,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 24718bd2d..87968254b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 1e7e91108..076263ba4 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,7 +132,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 0e4afa233..2bf0ef5f8 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -1,356 +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 - -我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 - - - -✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! - - + + +# 管道的内部 + +{#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 + +我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 + + + +✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! + + From 840c8e43760b7d187e8783aa5b3ae149267bc787 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 12 May 2022 23:33:49 +0200 Subject: [PATCH 154/789] multiple review comments applied --- chapters/fa/chapter3/2.mdx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 69b7e6b3e..fd134ebe6 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -195,7 +195,7 @@ raw_train_dataset.features {/if} -به منظور پیش‌پردازش دیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکنِایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِنایز کنیم: +به منظور پیش‌پردازش دیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکِنایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِن کنیم:
@@ -210,7 +210,7 @@ tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
-با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند که متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم، و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکِنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده‌سازی کند: +با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند که متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکِنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده‌سازی کند:
@@ -237,11 +237,11 @@ inputs -✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` را بردارید و دو جمله را به صورت جداگانه و جفت توکِنایز کنید. تفاوت دو نتیجه چیست؟ +✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` را بردارید و دو جمله را به صورت جداگانه و جفت توکِن کنید. تفاوت دو نتیجه چیست؟ -اگر شناسه‌های داخل `input_ids` را به کلمات رمزگشایی کنیم: +اگر شناسه‌های داخل `input_ids` را به کلمات کدگشایی کنیم:
@@ -253,9 +253,6 @@ tokenizer.convert_ids_to_tokens(inputs["input_ids"]) خواهیم داشت: -we will get: - -
```python out @@ -279,13 +276,13 @@ we will get: توجه داشته باشید که اگر نقطه تعلیم متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. -در اینجا، مدل BERT با آیدی‌هایی که نشان دهنده نوع توکن هستند پیش‌تعلیم شده، و علاوه بر هدف مدل سازی زبان ماسکی که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ی دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جفت جمله‌ها می‌باشد. +در اینجا، مدل BERT با شناسه‌هایی که نشان دهنده نوع توکِن هستند از پیش‌ تعلیم دیده و علاوه بر هدف تکمیل جاهای خالی متن که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین دو جمله می‌باشد. - در روش پیش‌بینی جمله بعدی، جفت جملاتی (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای خارج کردن مسئله از حالت بدیهی، در نیمی از حالت‌ها دوجمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. + در پیش‌بینی جمله بعدی، لیستی از جمله‌های جفت شده (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای سخت‌تر کردن مسئله، در نیمی از حالت‌ها دو جمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. -در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِنایز شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِن شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با یک جفت جمله برخورد کند، می‌توانیم آنرا برای توکِنایز کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با دو جمله برخورد کند، می‌توانیم آن را برای توکِن کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد:
@@ -300,9 +297,9 @@ tokenized_dataset = tokenizer(
-این روش به خوبی کار می‌کند، اما مشکل‌اش این است که یک دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids`, و مقادیر آنها که مجموعه‌ای از مجموعه‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکِنایز کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌می‌دارید). +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids` و مقادیر آنها که لیست‌هایی از لیست‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکِن کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [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.html#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌بر توکِن کردن نیاز داشته باشیم این روش انعطاف‌پذری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند:
@@ -313,9 +310,9 @@ def tokenize_function(example):
-این تابع یک دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از جفت جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان مجموعه‌ای از جملات) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [هاگینگ‌فِیس Tokenizers](https://github.com/huggingface/tokenizers) که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما مبحث `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام `padding` روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که `padding` را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. +توجه داشته باشید که ما آرگومان `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام padding روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که padding را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: @@ -351,7 +348,7 @@ DatasetDict({
-شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِنایز کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. From a88b0bba32ce7f37b94da8a4c2ca67b424eabffb Mon Sep 17 00:00:00 2001 From: Avishek Das Date: Mon, 16 May 2022 21:10:52 +0600 Subject: [PATCH 155/789] Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) --- chapters/bn/_toctree.yml | 5 +++++ chapters/bn/chapter2/1.mdx | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 chapters/bn/chapter2/1.mdx 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 From 2e2039527392b3be15c5614db94d0897dded8973 Mon Sep 17 00:00:00 2001 From: Suteera Seeha <33692408+meanna@users.noreply.github.com> Date: Mon, 16 May 2022 18:02:20 +0200 Subject: [PATCH 156/789] [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera --- chapters/th/_toctree.yml | 6 + chapters/th/chapter6/1.mdx | 21 +++ chapters/th/chapter6/2.mdx | 313 +++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 chapters/th/chapter6/1.mdx create mode 100644 chapters/th/chapter6/2.mdx 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 From dee8b18c5887aaa4299ee8ff6fd0364765579e96 Mon Sep 17 00:00:00 2001 From: Saeed Choobani Date: Mon, 16 May 2022 18:30:05 +0200 Subject: [PATCH 157/789] [FA] CH1 / P1-2 (#142) --- chapters/fa/_toctree.yml | 2 ++ chapters/fa/chapter1/1.mdx | 62 +++++++++++++++++++++++++++++++++++++- chapters/fa/chapter1/2.mdx | 25 +++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 chapters/fa/chapter1/2.mdx 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 From 1abf819395460b6aa91ec0c2b6831d4deb02492e Mon Sep 17 00:00:00 2001 From: Fermin Ordaz Date: Mon, 16 May 2022 10:26:13 -0700 Subject: [PATCH 158/789] Spanish Chapter 3: sections 1 & 2 (#162) --- chapters/es/_toctree.yml | 7 + chapters/es/chapter3/1.mdx | 21 ++ chapters/es/chapter3/2.mdx | 384 +++++++++++++++++++++++++++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 chapters/es/chapter3/1.mdx create mode 100644 chapters/es/chapter3/2.mdx 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} From e260263d2f1a7512de0bbc94448eb46682ba35d3 Mon Sep 17 00:00:00 2001 From: Kerem Turgutlu Date: Mon, 16 May 2022 12:15:47 -0700 Subject: [PATCH 159/789] fix typos in bpe, wordpiece, unigram (#166) --- chapters/en/chapter6/5.mdx | 2 +- chapters/en/chapter6/6.mdx | 4 ++-- chapters/en/chapter6/7.mdx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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.", From 1781826df488f2c5b7ec84780935c6bab993ac31 Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Mon, 16 May 2022 22:17:08 +0200 Subject: [PATCH 160/789] [FR] French Review (#186) --- chapters/en/chapter3/3.mdx | 2 +- chapters/en/chapter3/3_tf.mdx | 2 +- chapters/en/event/1.mdx | 4 +- 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 ++--- 69 files changed, 10943 insertions(+), 9745 deletions(-) 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 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/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/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. From ab803f377c7cbd42c2d895448337bd76746f4336 Mon Sep 17 00:00:00 2001 From: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Date: Tue, 17 May 2022 00:16:31 -0400 Subject: [PATCH 161/789] Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style --- chapters/en/chapter7/6.mdx | 13 ++++++++++--- chapters/en/chapter7/7.mdx | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) 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"]) ``` From 6c78c3e502bbd87638a1cd9f0d5418437dd96572 Mon Sep 17 00:00:00 2001 From: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Date: Tue, 17 May 2022 07:18:12 +0300 Subject: [PATCH 162/789] =?UTF-8?q?[TR]=20Translated=20Chapter=201.6=20?= =?UTF-8?q?=F0=9F=A4=97=20(#185)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 --- README.md | 1 + chapters/tr/_toctree.yml | 3 +++ chapters/tr/chapter1/6.mdx | 16 ++++++++++++++++ 3 files changed, 20 insertions(+) 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/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 1fe96c860802bd399740eb48cb1e5cfc5ab735b7 Mon Sep 17 00:00:00 2001 From: Victor Costa <54755870+victorescosta@users.noreply.github.com> Date: Tue, 17 May 2022 01:20:21 -0300 Subject: [PATCH 163/789] [PT][Chapter 01 - 2.mdx] - issue #51 (#170) --- chapters/pt/_toctree.yml | 2 ++ chapters/pt/chapter1/2.mdx | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 chapters/pt/chapter1/2.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 36fcacd23..da5d377ae 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -7,6 +7,8 @@ sections: - local: chapter1/1 title: Introdução + - local: chapter1/2 + title: Processamento de Linguagem Natural - title: 2. Usando 🤗 Transformers sections: 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 a1e7c0804d78a176314004984eaf9108f3afcade Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 17 May 2022 08:58:49 +0200 Subject: [PATCH 164/789] review changes applied --- chapters/fa/chapter3/2.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index fd134ebe6..564fff30d 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -312,7 +312,7 @@ def tokenize_function(example): این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما آرگومان `padding` را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که انجام padding روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که padding را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم که `padding` را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. +توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که آن را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم هم‌طول‌سازی را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: @@ -362,7 +362,7 @@ DatasetDict({ {#if fw === 'pt'} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش‌ فرض تابعی است که نمونه‌های شما را به ماتریس پایتورچ تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان‌پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش‌ فرض تابعی است که نمونه‌های شما را به ماتریس پایتورچ تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان‌پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {:else} @@ -370,7 +370,7 @@ DatasetDict({ {/if} -برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهند. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} @@ -467,7 +467,7 @@ batch = data_collator(samples) {#if fw === 'tf'} -توجه داشته باشید که ما دیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را کنار هم قرار دهیم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، یک روش ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این روش یک `tf.data.Dataset` را دور دیتاسِت‌تان می‌پیچد، با یک تابع `collation` دلخواه. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم! +اکنون که دیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را باهم بکار ببریم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، تابعی ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این تابع یک `tf.data.Dataset` شامل پارامتری اختیاری برای تابع `collation` را دور دیتاسِت‌تان می‌پیچد. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم!
From e9d680915fb35607f8e2fa951354badfcfe595cc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 18 May 2022 08:21:35 +0200 Subject: [PATCH 165/789] review coments applied --- chapters/fa/chapter3/2.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 564fff30d..b0746be86 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -312,9 +312,9 @@ def tokenize_function(example): این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. -توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در حال حاضر کنار گذاشته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که آن را زمانی که در حال ساختن بَتچ هستیم روی نمونه‌ها انجام دهیم، چرا که آنوقت فقط نیاز داریم هم‌طول‌سازی را روی همان بَتچ انجام دهیم، و نه بیشترین طول در سرتاسر دیتاسِت. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. +توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در تابع توکِن کننده‌مان نادیده گرفته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که نمونه‌ها را زمانی که در حال ساختن بَتچ هستیم هم‌طول کنیم، در این صورت فقط نیاز داریم نمونه‌ها را به اندازه بزرگترین طول همان بَتچ و نه بیشترین طول در سرتاسر دیتاسِت‌ هم‌طول کنیم. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع توکِنایزیشن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع تولید توکِن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد:
@@ -353,7 +353,7 @@ DatasetDict({ تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. -آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را باهم بَتچ می‌کنیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. +آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را با هم در یک بَتچ قرار می‌دهیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. ### هم‌طول‌سازی پویا @@ -366,11 +366,11 @@ DatasetDict({ {:else} -تابعی که مسئول کنارهم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. `collator` پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌ها ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. `collator` پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {/if} -برای انجام این کار در عمل، ما باید یک تابع `collate` تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم بَتچ کنیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار میدهد. این تابع به محض اینکه آنرا تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و اینکه مدل انتظار هم‌طول‌سازی از سمت چپ ورودی‌ها را داشته باشد یا از سمت راست آنها) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع مرتب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار می‌دهد. این تابع به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} @@ -398,7 +398,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" {/if} -اجازه دهید چند نمونه از مجموعه `training`مان را که می‌خواهیم باهم بَتچ کنیم برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1` و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: +اجازه دهید چند نمونه از مجموعه `training` را که می‌خواهیم باهم در یک بَتچ قرار دهیم برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1` و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ:
From d19424aeb4f69c2acedf74f34bd8061185c7812a Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 18 May 2022 12:18:28 +0200 Subject: [PATCH 166/789] Fix Gradio ToC (#193) --- chapters/en/_toctree.yml | 3 +-- chapters/fr/_toctree.yml | 15 +++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index 18ec59d4d..f8c2fe4c4 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: 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 From b9548e97d45a3e84e4063c4db58840729b0b3c72 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 18 May 2022 13:07:43 +0200 Subject: [PATCH 167/789] Add Gradio authors and Blocks event (#189) --- chapters/en/_toctree.yml | 2 + chapters/en/chapter1/1.mdx | 4 + chapters/en/chapter9/1.mdx | 8 +- chapters/en/chapter9/8.mdx | 239 ++----------------------------------- chapters/en/chapter9/9.mdx | 234 ++++++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 229 deletions(-) create mode 100644 chapters/en/chapter9/9.mdx diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index f8c2fe4c4..0ae46daa8 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -186,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 From fbc982306a7f8dda9060893eb848c81e91d01b4d Mon Sep 17 00:00:00 2001 From: Camille Couturier Date: Wed, 18 May 2022 20:47:18 +0100 Subject: [PATCH 168/789] Update 6.mdx (#188) Correct link to Transformer XL doc --- chapters/en/chapter1/6.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 58f52079c6decb876235700b638620a09c827222 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 19 May 2022 09:22:01 +0200 Subject: [PATCH 169/789] review coments applied --- chapters/fa/chapter3/2.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index b0746be86..c9601b3e1 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -276,13 +276,13 @@ tokenizer.convert_ids_to_tokens(inputs["input_ids"]) توجه داشته باشید که اگر نقطه تعلیم متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. -در اینجا، مدل BERT با شناسه‌هایی که نشان دهنده نوع توکِن هستند از پیش‌ تعلیم دیده و علاوه بر هدف تکمیل جاهای خالی متن که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ دیگری تحت عنوان _پیش‌بینی جمله‌ی بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین دو جمله می‌باشد. +در اینجا، مدل BERT با شناسه‌هایی که نشان دهنده نوع توکِن هستند از پیش‌ تعلیم دیده و علاوه بر هدف تکمیل جاهای خالی متن که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ دیگری تحت عنوان _پیش‌بینی جمله‌ بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جملات جفتی می‌باشد. در پیش‌بینی جمله بعدی، لیستی از جمله‌های جفت شده (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای سخت‌تر کردن مسئله، در نیمی از حالت‌ها دو جمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِن شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. -اکنون که مشاهده کردیم چگونه توکِنایزر ما می‌تواند با دو جمله برخورد کند، می‌توانیم آن را برای توکِن کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد: +اکنون که مشاهده کردیم چگونه توکِن کننده ما می‌تواند با دو جمله برخورد کند، می‌توانیم آن را برای توکِن کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد:
@@ -310,11 +310,11 @@ def tokenize_function(example):
-این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این توکِنایزر با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این `tokenizer` با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در تابع توکِن کننده‌مان نادیده گرفته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که نمونه‌ها را زمانی که در حال ساختن بَتچ هستیم هم‌طول کنیم، در این صورت فقط نیاز داریم نمونه‌ها را به اندازه بزرگترین طول همان بَتچ و نه بیشترین طول در سرتاسر دیتاسِت‌ هم‌طول کنیم. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. -در اینجا نشان می‌دهیم چگونه تابع تولید توکِن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابراین تابع روی چندین عنصر از دیتاسِت ما به یکباره عمل می‌کند نه روی هر عنصر به صورت جداگانه. این اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: +در اینجا نشان می‌دهیم چگونه تابع تولید توکِن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابر این تابع ما به جای اینکه روی هر عنصر به صورت جداگانه عمل کند روی چندین عنصر از دیتاسِت به یکباره عمل می‌کند. این کار اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد:
From af0bfc86368e317a88f19f3547d02d3b72582371 Mon Sep 17 00:00:00 2001 From: Fermin Ordaz Date: Thu, 19 May 2022 02:47:41 -0700 Subject: [PATCH 170/789] Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc --- chapters/es/TRANSLATING.txt | 23 +++++++++ chapters/es/_toctree.yml | 7 ++- chapters/es/glossary/1.mdx | 94 +++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 chapters/es/TRANSLATING.txt create mode 100644 chapters/es/glossary/1.mdx 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..a19b4763d 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 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. From 04d94444a434d355f6f27783fc32bd14a137255a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Thu, 19 May 2022 06:51:14 -0300 Subject: [PATCH 171/789] add pt 4.3 (#191) --- chapters/pt/_toctree.yml | 4 +- chapters/pt/chapter4/3.mdx | 641 +++++++++++++++++++++++++++++++++++++ 2 files changed, 644 insertions(+), 1 deletion(-) create mode 100644 chapters/pt/chapter4/3.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index da5d377ae..156a227f1 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -35,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 833ae2dc9bbc70d0922393a9be67c25aa7d0a4f4 Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Thu, 19 May 2022 11:51:58 +0200 Subject: [PATCH 172/789] [FR] Visual corrections (#190) --- 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 | 239 ++---------------------------------- chapters/fr/chapter9/9.mdx | 233 +++++++++++++++++++++++++++++++++++ 16 files changed, 293 insertions(+), 265 deletions(-) create mode 100644 chapters/fr/chapter9/9.mdx 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..3ee33a1fb 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -1,234 +1,17 @@ - +# Gradio, coché ! -# Quiz de fin de chapitre +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 : -Testons ce que vous avez appris dans ce chapitre ! +- à 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*. -### 1. Que pouvez-vous faire avec Gradio ? +Si vous souhaitez tester votre compréhension des concepts abordés dans ce chapitre, consultez le quiz dans la section suivante ! -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é.", - } - ]} -/> +## La Gradio blocks party 🥳 -### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch +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 ! -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 +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 From c90ad2a08d25570d9d4bbb52f359c707ad9dbfb0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 19 May 2022 13:40:09 +0200 Subject: [PATCH 173/789] review coments applied --- chapters/fa/chapter3/2.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c9601b3e1..872792503 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -297,7 +297,7 @@ tokenized_dataset = tokenizer(
-این روش به خوبی کار می‌کند، اما مشکل‌اش این است که دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids` و مقادیر آنها که لیست‌هایی از لیست‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکِن کردن داشته باشید (در حالی که دیتاسِت‌های موجود در پایگاه دیتاسِت هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌ می‌دارید). +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که دیکشنری (از کلیدهای ما شامل، `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()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند: @@ -325,7 +325,7 @@ tokenized_datasets
-کتابخانه دیتاسِت هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: +کتابخانه `Datasets` از هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند:
@@ -348,7 +348,7 @@ DatasetDict({
-شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه توکِنایزر هاگینگ‌فِیس از پیش از چندین رشته پردازنده برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازنده برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. From 822691848cafed350b5a5ab519ceb3daefcc1015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Thu, 19 May 2022 09:13:04 -0300 Subject: [PATCH 174/789] [PT] add chapter 4.4 and 4.5 (#196) --- chapters/pt/_toctree.yml | 4 ++ chapters/pt/chapter4/4.mdx | 82 ++++++++++++++++++++++++++++++++++++++ chapters/pt/chapter4/5.mdx | 7 ++++ 3 files changed, 93 insertions(+) create mode 100644 chapters/pt/chapter4/4.mdx create mode 100644 chapters/pt/chapter4/5.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 156a227f1..5116a9e61 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -38,3 +38,7 @@ 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! 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! From b6a27742fa33fb94e2723b612ba3791858a4dd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Thu, 19 May 2022 18:44:05 -0300 Subject: [PATCH 175/789] fix invite discord link (#197) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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** From 0423a4229a447e88ca219577d19dd9367d82fb5c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 20 May 2022 09:08:39 +0200 Subject: [PATCH 176/789] review changes applied --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 872792503..c82b29e72 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -348,7 +348,7 @@ DatasetDict({
-شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از پردازش موازی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازنده برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از چندپردازشی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازشی برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. From 1302cefb911ea97e191f8495c442e9b7fcaba8d2 Mon Sep 17 00:00:00 2001 From: Bahram Shamshiri Date: Fri, 20 May 2022 14:39:16 +0430 Subject: [PATCH 177/789] [FA] Second draft of CH2/P1-2 (#139) --- chapters/fa/TRANSLATING.txt | 3 ++ chapters/fa/chapter2/1.mdx | 24 +++++----- chapters/fa/chapter2/2.mdx | 87 ++++++++++++++++++++----------------- chapters/fa/glossary/1.mdx | 23 +++++++--- 4 files changed, 77 insertions(+), 60 deletions(-) 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/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/glossary/1.mdx b/chapters/fa/glossary/1.mdx index fe42e4885..6e0ef2dba 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,18 @@ | 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 | نمونه اولیه | معادل‌هایی که استفاده نمی‌کنیم: @@ -157,5 +167,6 @@ | TPU | TPU | | BERT | BERT | | ML | یادگیری ماشین | +| AGI | هوش جامع مصنوعی |
From 8b786d174c68f0ae8c01587142b86ff1984c687a Mon Sep 17 00:00:00 2001 From: Kavya <36916536+robotjellyzone@users.noreply.github.com> Date: Fri, 20 May 2022 15:40:23 +0530 Subject: [PATCH 178/789] added chapter3 in hindi (#198) --- chapters/hi/_toctree.yml | 19 +- 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 ++++++++++++++++++++++++++ 8 files changed, 1466 insertions(+), 1 deletion(-) 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 diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index d545861ea..4c2a81f46 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -11,4 +11,21 @@ - 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 \ No newline at end of file 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} From fe3df6d824a3b16dc4fdd4199f47ff70c694714b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 20 May 2022 12:56:09 +0200 Subject: [PATCH 179/789] finish applying review comments --- chapters/fa/chapter3/2.mdx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index c82b29e72..6759d50c9 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -348,12 +348,12 @@ DatasetDict({
-شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از چندپردازشی استفاده کنید. ما آن را در اینجا استفاده نکردیم به این خاطر که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازشی برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما در صورتی که از یک توکِنایزر سریع که با این کتابخانه پشتیبانی می‌شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از چندپردازشی استفاده کنید. در اینجا ما این کار را انجام ندادیم چرا که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازشی برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما اگر شما از یک توکِنایزر سریع که با این کتابخانه پشتیبانی شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. -تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids`، برمی‌گرداند به گونه‌ای که این سه فیلد به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید که اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت که تابع `map()` به آن اعمال شده یک مقدار جدید بازمی‌گردانید همچنین می‌توانستیم فیلدهای موجود را تغییر دهیم. +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند به گونه‌ای که این کلیدها به صورت سه فیلد جدید به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت مقدار جدیدی بازمی‌گرداند ما می‌توانستیم فیلدهای موجود در دیتاسِتی که تابع `map()` به آن اعمال می‌شود را نیز تغییر دهیم. -آخرین چیزی که نیاز داریم انجام دهیم این است که هنگامی که عناصر را با هم در یک بَتچ قرار می‌دهیم، همه نمونه‌ها را به اندازه طول بلندترین عنصر هم‌طول کنیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. +آخرین کاری که باید انجام دهیم این است که هنگامی که عناصر را با هم در یک بَتچ قرار می‌دهیم، طول همه عناصر را به اندازه بلندترین عنصر برسانیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. ### هم‌طول‌سازی پویا @@ -362,11 +362,14 @@ DatasetDict({ {#if fw === 'pt'} -تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. این تابع آرگومانی است که شما می‌توانید هنگام ساختن یک `DataLoader` به داخل آن ارسال کنید، که در حالت پیش‌ فرض تابعی است که نمونه‌های شما را به ماتریس پایتورچ تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان‌پذیر نیست. ما پروسه هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آنرا روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش پروسه تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید می‌تواند مشکل ساز باشد - TPUها اشکال معین را ترجیح می‌دهند، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع مرتب کننده* خوانده می‌شود. شما می‌توانید این تابع را که در حالت پیش‌ فرض نمونه‌های شما را به تِنسور پایتورچ تبدیل کرده و به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد) هنگام ساختن `DataLoader` به داخل آن ارسال کنید. از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان‌پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + {:else} -تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *collate function* خوانده می‌شود. `collator` پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را بهم الحاق می‌کند (اگر عناصر شما لیست، تاپل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما همه به یک اندازه نیستند این کار برای ما امکان پذیر نیست. ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم، تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با تعداد زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع مرتب کننده* خوانده می‌شود. تابع مرتب کننده پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + +The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. {/if} From b748da27d9dc94215549ca5260d9327cba7776eb Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 20 May 2022 13:21:26 +0200 Subject: [PATCH 180/789] PR failure resolved --- chapters/fa/_toctree.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 9853f9d85..d45d23d0e 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -32,7 +32,6 @@ title: مقدمه # Translate this! - local: chapter3/2 title: پردازش داده # Translate this! - local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - title: واژه‌نامه sections: From dc51a8cf322330d4a4dbcb9636d2ce7cea121ed3 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sat, 21 May 2022 18:40:37 +0200 Subject: [PATCH 181/789] minot typo --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 6759d50c9..92dc1c469 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,7 +1,7 @@
-# پردازش داده +#پردازش داده {#if fw === 'pt'} From 9708bc9c6df532b812562f148ecbd98a5144a973 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sat, 21 May 2022 18:41:41 +0200 Subject: [PATCH 182/789] minot typo --- chapters/fa/chapter3/2.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 92dc1c469..cf9a9e1de 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -1,7 +1,8 @@
-#پردازش داده + +# پردازش داده {#if fw === 'pt'} From 8a5894fc0695cf47ca8ca691c6911dbef6ec3c1e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 22 May 2022 13:00:03 +0200 Subject: [PATCH 183/789] new phrases added to G --- chapters/fa/glossary/1.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 6e0ef2dba..4813de422 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -145,6 +145,17 @@ | Naive Bayes | بیز ساده | | Collaborative learning | یادگیری مشارکتی | | Demo | نمونه اولیه | +| collate | مرتب کردن | +| mapping | نگاشت | +| element | عنصر | +| tuple | تاپِل | +| object | شیء | +| paraphrases | جملات متناظر | +| benchmark | محک | +| items | اقلام | +| padding | هم‌طول‌سازی | +| sequential model | مدل ترتیبی | +| documentation | مستندات | معادل‌هایی که استفاده نمی‌کنیم: From 8d6f07345ca5ff1c7a38fedd2214c6c24f9061e8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 23 May 2022 08:29:02 +0200 Subject: [PATCH 184/789] a few changes applied --- chapters/fa/chapter3/2.mdx | 13 +++++-------- chapters/fa/glossary/1.mdx | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cf9a9e1de..cfe504239 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -101,7 +101,7 @@ model.train_on_batch(batch, labels) هاب تنها شامل مدل‌ها نمی‌باشد؛ بلکه شامل دیتاسِت‌های متعدد در بسیاری از زبان‌های مختلف می‌باشد. شما می‌توانید دیتاسِت‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک دیتاسِت جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی دیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. -کتابخانه دیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و ذخیره‌سازی یک دیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دیتاسِت MRPC را به روش زیر دانلود کنیم: +کتابخانه دیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و انبار کردن یک دیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دیتاسِت MRPC را به روش زیر دانلود کنیم:
@@ -137,7 +137,7 @@ DatasetDict({ همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). - این دستور دیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ *~/.cache/huggingface/dataset* ذخیره میکند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ ذخیره‌سازی‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + این دستور دیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ *~/.cache/huggingface/dataset* انبار می‌کند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ انبار کردن‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس, مانند یک دیکشنری دسترسی پیدا کنیم: @@ -363,18 +363,15 @@ DatasetDict({ {#if fw === 'pt'} -تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع مرتب کننده* خوانده می‌شود. شما می‌توانید این تابع را که در حالت پیش‌ فرض نمونه‌های شما را به تِنسور پایتورچ تبدیل کرده و به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد) هنگام ساختن `DataLoader` به داخل آن ارسال کنید. از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان‌پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. - +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع ترکیب کننده* خوانده می‌شود. شما می‌توانید این تابع را که در حالت پیش‌ فرض نمونه‌های شما را به تِنسور پایتورچ تبدیل کرده و به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد) هنگام ساختن `DataLoader` به داخل آن ارسال کنید. از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان‌پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {:else} -تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع مرتب کننده* خوانده می‌شود. تابع مرتب کننده پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. - -The function that is responsible for putting together samples inside a batch is called a *collate function*. The default collator is a function that will just convert your samples to tf.Tensor and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won't be possible in our case since the inputs we have won't all be of the same size. We have deliberately postponed the padding, to only apply it as necessary on each batch and avoid having over-long inputs with a lot of padding. This will speed up training by quite a bit, but note that if you're training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding. +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع ترکیب کننده* خوانده می‌شود. تابع ترکیب کننده پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. {/if} -برای انجام این کار در عمل، ما باید یک تابع مرتب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین تابعی را تحت عنوان `DataCollatorWithPadding` در اختیار ما قرار می‌دهد. این تابع به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط شیء `DataCollatorWithPadding` به ما می‌دهد. این تابع به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 4813de422..d3fedc968 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -145,7 +145,7 @@ | Naive Bayes | بیز ساده | | Collaborative learning | یادگیری مشارکتی | | Demo | نمونه اولیه | -| collate | مرتب کردن | +| collate | ترکیب کردن | | mapping | نگاشت | | element | عنصر | | tuple | تاپِل | @@ -154,7 +154,6 @@ | benchmark | محک | | items | اقلام | | padding | هم‌طول‌سازی | -| sequential model | مدل ترتیبی | | documentation | مستندات | معادل‌هایی که استفاده نمی‌کنیم: From 494af448ff3e61884de48d6df83d06d4c31c7447 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 23 May 2022 08:53:18 +0200 Subject: [PATCH 185/789] minor updates applied --- chapters/fa/chapter3/2.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index cfe504239..1468d8a9e 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -135,7 +135,7 @@ DatasetDict({
-همانطور که می بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). +همانطور که می‌بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). این دستور دیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ *~/.cache/huggingface/dataset* انبار می‌کند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ انبار کردن‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. @@ -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.html#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌ بر توکِن کردن نیاز داشته باشیم این روش انعطاف‌پذیری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند:
@@ -311,7 +311,7 @@ def tokenize_function(example):
-این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکٍنایزر را به میزان زیادی سریع‌تر خواهد کرد. این `tokenizer` با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکٍنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یکجا به آن بدهیم. +این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکِنایزر را به میزان زیادی سریع‌تر خواهد کرد. این `tokenizer` با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکِنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یک جا به آن بدهیم. توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در تابع توکِن کننده‌مان نادیده گرفته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که نمونه‌ها را زمانی که در حال ساختن بَتچ هستیم هم‌طول کنیم، در این صورت فقط نیاز داریم نمونه‌ها را به اندازه بزرگترین طول همان بَتچ و نه بیشترین طول در سرتاسر دیتاسِت‌ هم‌طول کنیم. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. @@ -371,7 +371,7 @@ DatasetDict({ {/if} -برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط شیء `DataCollatorWithPadding` به ما می‌دهد. این تابع به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط شیء `DataCollatorWithPadding` به ما می‌دهد. این شئ به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} @@ -456,19 +456,19 @@ batch = data_collator(samples)
-به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده هستیم برای کوک‌ کردن مدل: +به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده کوک‌ کردن مدل هستیم: {/if} -✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای جفت شامل تک جمله‌ها می‌باشد این کار یک مقدار متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای دو جمله‌ها شامل تک جمله‌ها می‌باشد این کار کمی متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. {#if fw === 'tf'} -اکنون که دیتاسِت‌مان و یک `collator` داده در اختیار داریم، حال نیاز داریم که آنها را باهم بکار ببریم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، تابعی ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این تابع یک `tf.data.Dataset` شامل پارامتری اختیاری برای تابع `collation` را دور دیتاسِت‌تان می‌پیچد. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در تنیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم! +اکنون که دیتاسِت‌مان و یک `collator` داده در اختیار داریم، نیاز داریم که آنها را باهم بکار ببریم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، تابعی ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این تابع یک `tf.data.Dataset` شامل پارامتری اختیاری برای تابع `collation` را دور دیتاسِت‌تان می‌پیچد. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در نتیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم!
From 703d1d138069bc0ac6083bee018a7849eed55234 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 23 May 2022 14:21:27 +0200 Subject: [PATCH 186/789] comments applied --- chapters/fa/chapter3/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 1468d8a9e..54dc5dedc 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -161,7 +161,7 @@ raw_train_dataset[0]
-می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین لازم نیست هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم قابلیت‌های ‌`raw_train_dataset`‌ را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. +می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین لازم نیست هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم `features` از ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت.
@@ -371,7 +371,7 @@ DatasetDict({ {/if} -برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط شیء `DataCollatorWithPadding` به ما می‌دهد. این شئ به محض این که آن را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط کلاس `DataCollatorWithPadding` به ما می‌دهد. به محض این که این کلاس را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} From 42d203089919dad32385f76500914336a8e35ee7 Mon Sep 17 00:00:00 2001 From: Batuhan Ayhan Date: Mon, 23 May 2022 21:50:21 +0300 Subject: [PATCH 187/789] [TR] Chapter 3/1 (#165) --- chapters/tr/_toctree.yml | 6 +++++- chapters/tr/chapter3/1.mdx | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 chapters/tr/chapter3/1.mdx 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 a340c810bd44e60cc175abd3d91b5961daa37c53 Mon Sep 17 00:00:00 2001 From: Pavel <60391448+pdumin@users.noreply.github.com> Date: Mon, 23 May 2022 21:56:07 +0300 Subject: [PATCH 188/789] [RU] Ch3-1/2/3 (#200) --- chapters/ru/_toctree.yml | 2 + chapters/ru/chapter3/4.mdx | 363 +++++++++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 chapters/ru/chapter3/4.mdx 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). From b09e5faf02b561b02532c7d5c3411d5040d2c91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Mon, 23 May 2022 16:37:25 -0300 Subject: [PATCH 189/789] [PT] add 5.1 and 5.2 (#204) --- chapters/pt/_toctree.yml | 8 ++ chapters/pt/chapter5/1.mdx | 18 ++++ chapters/pt/chapter5/2.mdx | 167 +++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 chapters/pt/chapter5/1.mdx create mode 100644 chapters/pt/chapter5/2.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 5116a9e61..e765fa888 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -42,3 +42,11 @@ title: Construindo um cartão para o modelo - local: chapter4/5 title: Parte 1 completa! + +- 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? + \ No newline at end of file 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). + + + From 31b55cd9fd543405d0258b5aab511c3044b0a3a8 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 24 May 2022 09:12:13 +0200 Subject: [PATCH 190/789] one last fix --- chapters/fa/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 54dc5dedc..092a92b30 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -371,7 +371,7 @@ DatasetDict({ {/if} -برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط کلاس `DataCollatorWithPadding` به ما می‌دهد. به محض این که این کلاس را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: +برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط کلاس `DataCollatorWithPadding` به ما می‌دهد. به محض این که شیء‌ای از این کلاس را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: {#if fw === 'pt'} From e55809abd73beeb36fc8768fb842281bc3ee9a1f Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 24 May 2022 18:30:04 +0200 Subject: [PATCH 191/789] Add placeholders for audio chapters (#208) --- chapters/en/_toctree.yml | 22 +++- chapters/en/chapter10/1.mdx | 14 +++ chapters/en/chapter10/2.mdx | 7 ++ chapters/en/chapter10/3.mdx | 3 + chapters/en/chapter10/4.mdx | 1 + chapters/en/chapter10/5.mdx | 1 + chapters/en/chapter10/6.mdx | 1 + chapters/en/chapter10/7.mdx | 1 + chapters/en/chapter10/8.mdx | 234 ++++++++++++++++++++++++++++++++++++ 9 files changed, 283 insertions(+), 1 deletion(-) 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/en/chapter10/8.mdx diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index 0ae46daa8..7cae8efbe 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -168,7 +168,6 @@ quiz: 8 - title: 9. Building and sharing demos - new: true subtitle: I trained a model, but how can I show it off? sections: - local: chapter9/1 @@ -191,6 +190,27 @@ title: End-of-chapter quiz quiz: 9 +- title: 10. Transformers can hear + new: true + sections: + - local: chapter10/1 + title: Introduction + - local: chapter10/2 + title: Why use Transformer for audio? + - local: chapter10/3 + title: Training your first audio Transformer + - local: chapter10/4 + title: Automatically recognizing speech + - local: chapter10/5 + title: Converting text to speech + - local: chapter10/6 + title: Audio to audio + - local: chapter10/7 + title: Audio transformers, check! + - local: chapter10/8 + title: End-of-chapter quiz + quiz: 10 + - title: Hugging Face Course Event sections: - local: event/1 diff --git a/chapters/en/chapter10/1.mdx b/chapters/en/chapter10/1.mdx new file mode 100644 index 000000000..898965322 --- /dev/null +++ b/chapters/en/chapter10/1.mdx @@ -0,0 +1,14 @@ +# Going beyond text + +So far in this course, we've seen that Transformers are exceptionally good at tackling a wide variety of tasks in NLP. But did you know that Transformers can also be applied to entirely different _modalities_ like audio and images? In this chapter, we will explore how Transformers can process _audio waveforms_ to produce novel applications like speech recognition. + +After explaining why Transformers might be suitable for audio, each of the sections in this chapter will dive into some of the most common tasks in this fast-moving field: + +* Classifying audio signals into a set of categories. +* Transcribing speech to text +* Converting text to speech +* Bypassing text altogether and converting audio to audio + +To do this, you'll need to leverage everything you learned about the `Trainer` API library in [Chapter 3](/course/chapter3), the 🤗 Datasets library in [Chapter 5](/course/chapter5), and the Gradio library in [Chapter 9](/course/chapter9). So go read those chapters first if you haven't done so already. + +Let's now dive into the fascinating world of audio! \ No newline at end of file diff --git a/chapters/en/chapter10/2.mdx b/chapters/en/chapter10/2.mdx new file mode 100644 index 000000000..089a55eb3 --- /dev/null +++ b/chapters/en/chapter10/2.mdx @@ -0,0 +1,7 @@ +# Why use Transformers for audio? + +Random ideas: + +* A brief bit of history on pre-transformer approaches? +* Mention popular architectures like wav2vec2, xsl-r etc +* Show pipelines for most common audio tasks? \ No newline at end of file diff --git a/chapters/en/chapter10/3.mdx b/chapters/en/chapter10/3.mdx new file mode 100644 index 000000000..ad6b7b913 --- /dev/null +++ b/chapters/en/chapter10/3.mdx @@ -0,0 +1,3 @@ +# Training your first audio transformer + +Place holder for audio classification \ No newline at end of file diff --git a/chapters/en/chapter10/4.mdx b/chapters/en/chapter10/4.mdx new file mode 100644 index 000000000..9f47e6b32 --- /dev/null +++ b/chapters/en/chapter10/4.mdx @@ -0,0 +1 @@ +# Automatically recognizing speech \ No newline at end of file diff --git a/chapters/en/chapter10/5.mdx b/chapters/en/chapter10/5.mdx new file mode 100644 index 000000000..9d8fecef4 --- /dev/null +++ b/chapters/en/chapter10/5.mdx @@ -0,0 +1 @@ +# Converting text to speech \ No newline at end of file diff --git a/chapters/en/chapter10/6.mdx b/chapters/en/chapter10/6.mdx new file mode 100644 index 000000000..190c42e06 --- /dev/null +++ b/chapters/en/chapter10/6.mdx @@ -0,0 +1 @@ +# Audio to audio \ 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..c9f10f2b8 --- /dev/null +++ b/chapters/en/chapter10/7.mdx @@ -0,0 +1 @@ +# Audio transformers, check! \ No newline at end of file diff --git a/chapters/en/chapter10/8.mdx b/chapters/en/chapter10/8.mdx new file mode 100644 index 000000000..b5e73698b --- /dev/null +++ b/chapters/en/chapter10/8.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 From 3b081e9196973e79b01016e96cf1f9edb9086183 Mon Sep 17 00:00:00 2001 From: Kambiz Ghoorchian Date: Tue, 24 May 2022 21:11:43 +0200 Subject: [PATCH 192/789] [FA] - Ch3 - P1 and P2 (#199) --- chapters/fa/_toctree.yml | 7 + chapters/fa/chapter3/1.mdx | 27 ++ chapters/fa/chapter3/2.mdx | 501 +++++++++++++++++++++++++++++++++++++ chapters/fa/glossary/1.mdx | 10 + 4 files changed, 545 insertions(+) create mode 100644 chapters/fa/chapter3/1.mdx create mode 100644 chapters/fa/chapter3/2.mdx 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/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 6e0ef2dba..d3fedc968 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -145,6 +145,16 @@ | Naive Bayes | بیز ساده | | Collaborative learning | یادگیری مشارکتی | | Demo | نمونه اولیه | +| collate | ترکیب کردن | +| mapping | نگاشت | +| element | عنصر | +| tuple | تاپِل | +| object | شیء | +| paraphrases | جملات متناظر | +| benchmark | محک | +| items | اقلام | +| padding | هم‌طول‌سازی | +| documentation | مستندات | معادل‌هایی که استفاده نمی‌کنیم: From 51a6c9fea68d7ead1fa11c842c08d61f68489f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Tue, 24 May 2022 16:20:11 -0300 Subject: [PATCH 193/789] [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun --- chapters/pt/_toctree.yml | 4 +- chapters/pt/chapter4/6.mdx | 223 +++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 chapters/pt/chapter4/6.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index e765fa888..9ea9adab5 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -42,6 +42,9 @@ 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: @@ -49,4 +52,3 @@ title: Introdução - local: chapter5/2 title: E se o meu dataset não estiver no Hub? - \ No newline at end of file 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." + } + ]} +/> From 751d0690d3480224fb160c3a552e5b28376a313a Mon Sep 17 00:00:00 2001 From: Vedant Pandya Date: Wed, 25 May 2022 00:51:37 +0530 Subject: [PATCH 194/789] Chapter1: 2.mdx Translated. (#206) --- chapters/hi/_toctree.yml | 4 +++- chapters/hi/chapter0/1.mdx | 8 ++++---- chapters/hi/chapter1/2.mdx | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 chapters/hi/chapter1/2.mdx diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index 4c2a81f46..f8f856b4b 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -7,6 +7,8 @@ sections: - local: chapter1/1 title: परिचय + - local: chapter1/2 + title: प्राकृतिक भाषा प्रसंस्करण - title: 2. ट्रांसफॉर्मर का उपयोग करना sections: @@ -28,4 +30,4 @@ title: फाइन-ट्यूनिंग, चेक! - local: chapter3/6 title: अध्याय-का-अंत प्रश्नोत्तरी - quiz: 3 \ No newline at end of file + 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 @@ +# प्राकृतिक भाषा प्रसंस्करण + +ट्रांसफॉर्मर मॉडल में जाने से पहले, आइए एक त्वरित अवलोकन करें कि प्राकृतिक भाषा प्रसंस्करण क्या है और हम इसकी परवाह क्यों करते हैं। + +## प्राकृतिक भाषा प्रसंस्करण क्या है? + +प्राकृतिक भाषा प्रसंस्करण भाषा विज्ञान और मशीन सीखने का एक क्षेत्र है जो मानव भाषा से संबंधित हर चीज को समझने पर केंद्रित है। एनएलपी कार्यों का उद्देश्य न केवल एक शब्द को व्यक्तिगत रूप से समझना है, बल्कि उन शब्दों के संदर्भ को समझने में सक्षम होना है। + +निम्नलिखित सामान्य प्राकृतिक भाषा प्रसंस्करण कार्यों की एक सूची है, जिनमें से प्रत्येक के कुछ उदाहरण हैं: +- **पूरे वाक्यों को वर्गीकृत करना**: समीक्षा की भावना प्राप्त करना, यह पता लगाना कि क्या कोई ईमेल स्पैम है, यह निर्धारित करना कि कोई वाक्य व्याकरणिक रूप से सही है या दो वाक्य तार्किक रूप से संबंधित हैं या नहीं। +- **प्रत्येक शब्द को एक वाक्य में वर्गीकृत करना**: एक वाक्य (संज्ञा, क्रिया, विशेषण), या नामित संस्थाओं (व्यक्ति, स्थान, संगठन) के व्याकरणिक घटकों की पहचान करना। +- **पाठ सामग्री उत्पन्न करना**: ऑटो-जेनरेटेड टेक्स्ट के साथ एक प्रॉम्प्ट को पूरा करना, टेक्स्ट में रिक्त स्थान को नकाबपोश शब्दों से भरना। +- **किसी पाठ से उत्तर निकालना**: एक प्रश्न और एक संदर्भ को देखते हुए, संदर्भ में दी गई जानकारी के आधार पर प्रश्न का उत्तर निकालना। +- **इनपुट टेक्स्ट से एक नया वाक्य बनाना**: एक पाठ को दूसरी भाषा में अनुवाद करना, एक पाठ को सारांशित करना। + +प्राकृतिक भाषा प्रसंस्करण हालांकि लिखित पाठ तक ही सीमित नहीं है। यह वाक् पहचान और कंप्यूटर विज़न में जटिल चुनौतियों से भी निपटता है, जैसे कि ऑडियो नमूने की प्रतिलिपि बनाना या किसी छवि का विवरण। + +## यह चुनौतीपूर्ण क्यों है? + +कंप्यूटर इंसानों की तरह सूचनाओं को प्रोसेस नहीं करते हैं। उदाहरण के लिए, जब हम "मुझे भूख लगी है" वाक्य पढ़ते हैं, तो हम इसका अर्थ आसानी से समझ सकते हैं। इसी तरह, "मैं भूखा हूँ" और "मैं उदास हूँ" जैसे दो वाक्यों को देखते हुए, हम आसानी से यह निर्धारित करने में सक्षम हैं कि वे कितने समान हैं। मशीन लर्निंग (एमएल) मॉडल के लिए, ऐसे कार्य अधिक कठिन होते हैं। पाठ को इस तरह से संसाधित करने की आवश्यकता है जो मॉडल को इससे सीखने में सक्षम बनाता है। और क्योंकि भाषा जटिल है, हमें ध्यान से सोचने की जरूरत है कि यह प्रसंस्करण कैसे किया जाना चाहिए। पाठ का प्रतिनिधित्व करने के तरीके पर बहुत शोध किया गया है, और हम अगले अध्याय में कुछ विधियों को देखेंगे। From 9bf50f0a714d3a086b52cce8bb66cb5c65c58eb4 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 24 May 2022 21:44:58 +0200 Subject: [PATCH 195/789] Remove comments from Persian ToC (#210) --- chapters/fa/_toctree.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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: From b16fdff428b0b09e9b44e5492d130fc98aafddad Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 24 May 2022 22:16:41 +0200 Subject: [PATCH 196/789] Fix CI URL for PRs (#211) --- .github/workflows/build_pr_documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From db514469d8fabb83f929693ec64b0720083434b5 Mon Sep 17 00:00:00 2001 From: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Date: Wed, 25 May 2022 05:59:21 -0400 Subject: [PATCH 197/789] code fragment & english syntax and meaning (#203) --- chapters/en/chapter8/4.mdx | 2 +- chapters/en/chapter8/7.mdx | 2 +- utils/generate_notebooks.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) 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/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 ef316d833788b217b377cf85fd490d403cca84b7 Mon Sep 17 00:00:00 2001 From: Vedant Pandya Date: Wed, 25 May 2022 15:30:02 +0530 Subject: [PATCH 198/789] Updated Ch1/1 with Emoji (#214) --- chapters/hi/chapter1/1.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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/) का सह-लेखन किया जेरेमी हॉवर्ड के साथ। उनके शोध का मुख्य फोकस तकनीकों को डिजाइन और सुधार करके गहन शिक्षण को और अधिक सुलभ बनाने पर है जो मॉडल को सीमित संसाधनों पर तेजी से प्रशिक्षित करने की अनुमति देता है। **मर्व नोयान** हगिंग फेस में एक डेवलपर एडवोकेट है, जो सभी के लिए मशीन लर्निंग का लोकतंत्रीकरण करने के लिए टूल विकसित करने और उनके आसपास सामग्री बनाने पर काम कर रहे है। From 62a824bed4ae6a387cef41625285f4b39bf04ed2 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 25 May 2022 21:34:55 +0200 Subject: [PATCH 199/789] Add missing numpy import (#217) --- chapters/en/chapter6/3b.mdx | 2 ++ 1 file changed, 2 insertions(+) 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) ``` From 7753f6a694bdb9bd0e64ab91fe44a3830e7c0519 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Thu, 26 May 2022 12:01:02 +0800 Subject: [PATCH 200/789] Updata chapter3 --- 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 | 189 +++++++++++++++ chapters/zh-CN/chapter3/4.mdx | 358 +++++++++++++++++++++++++++++ chapters/zh-CN/chapter3/5.mdx | 20 ++ chapters/zh-CN/chapter3/6.mdx | 284 +++++++++++++++++++++++ 7 files changed, 1427 insertions(+) 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/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..ac98487f8 --- /dev/null +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -0,0 +1,189 @@ + + +# 使用 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 From 5f5c83424a496699a948867d100f9198834aa074 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Thu, 26 May 2022 12:04:18 +0800 Subject: [PATCH 201/789] Code format for chapter3 From 3885ff731e372c6c40a1d2b316e787a91ffb6260 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Thu, 26 May 2022 12:49:50 +0800 Subject: [PATCH 202/789] Updata yml file of chapter3 From 76c59b210597453352e1dc1910ab4415d5060f9a Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Thu, 26 May 2022 13:01:55 +0800 Subject: [PATCH 203/789] Uptata yml file of chapter3 --- chapters/zh-CN/_toctree.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index ea5134bf3..765c7f0d7 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 \ No newline at end of file From 54b4791d254b11c7f3d9b4c388557bb9a4a8609e Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Thu, 26 May 2022 13:12:34 +0800 Subject: [PATCH 204/789] Fix yml file bug --- chapters/zh-CN/_toctree.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 765c7f0d7..39883a89e 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -47,7 +47,7 @@ title: 章末小测验 quiz: 2 - - title: 3. 微调一个预训练模型 +- title: 3. 微调一个预训练模型 sections: - local: chapter3/1 title: 章节简介 @@ -62,4 +62,4 @@ title: 微调,章节回顾! - local: chapter3/6 title: 章末小测验 - quiz: 3 \ No newline at end of file + quiz: 3 From bc296c54dab8337430428be0a53b4c03bdbec397 Mon Sep 17 00:00:00 2001 From: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Date: Fri, 27 May 2022 10:13:48 -0500 Subject: [PATCH 205/789] [ES] translate sections 8.1 and 8.2 (#215) --- chapters/es/_toctree.yml | 9 + chapters/es/chapter8/1.mdx | 12 ++ chapters/es/chapter8/2.mdx | 375 +++++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 chapters/es/chapter8/1.mdx create mode 100644 chapters/es/chapter8/2.mdx diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index a19b4763d..0250693da 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -40,6 +40,15 @@ 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: 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. From 4de96522a86eec30a22fdb4f893c4d3a490964ce Mon Sep 17 00:00:00 2001 From: Thomas O'Brien Date: Fri, 27 May 2022 11:14:12 -0400 Subject: [PATCH 206/789] Fix path to datasets (#216) --- chapters/en/chapter3/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 8545f0550cc24820be25291e51c9c03f0cb869d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Fri, 27 May 2022 12:15:49 -0300 Subject: [PATCH 207/789] [PT] add 5.3 (#218) --- chapters/pt/_toctree.yml | 3 + chapters/pt/chapter5/3.mdx | 742 +++++++++++++++++++++++++++++++++++++ 2 files changed, 745 insertions(+) create mode 100644 chapters/pt/chapter5/3.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 9ea9adab5..02de3dd5f 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -52,3 +52,6 @@ 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/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! From f66182e9b343dabda4cf8db20a3c6b3e7c8b8d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Fri, 27 May 2022 12:18:58 -0300 Subject: [PATCH 208/789] fix 4.3 (#223) --- chapters/pt/chapter4/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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} From 4f1091191b8bc9a053546861101ecf6a892065f1 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Sat, 28 May 2022 09:08:26 +0800 Subject: [PATCH 209/789] Run make style --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 3 +- chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/fr/chapter3/3_tf.mdx | 3 +- chapters/fr/chapter5/4.mdx | 592 +++++++++++++++++----------------- chapters/fr/chapter6/8.mdx | 4 +- chapters/fr/chapter7/2.mdx | 17 +- chapters/fr/chapter7/4.mdx | 5 +- chapters/fr/chapter7/5.mdx | 3 +- chapters/fr/chapter7/7.mdx | 5 +- chapters/hi/chapter3/3_tf.mdx | 3 +- chapters/it/chapter1/3.mdx | 4 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 5 +- 28 files changed, 329 insertions(+), 398 deletions(-) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index dd1be7835..566457a0e 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..cd6aee466 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..313c1fc53 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 2252a9613..6d43884e4 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..b7d2609f7 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..c7cef7308 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,9 +404,7 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 9d1ccc3b9..5a99e497c 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,9 +413,7 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -663,9 +661,7 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -774,10 +770,7 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -788,9 +781,7 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 5aa654ceb..2d599c3c7 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,10 +795,7 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 1f9280c15..f344f7b10 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -928,8 +928,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d8e1942e4..8e18ac50b 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,10 +1029,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..04ac7f60a 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 71abc5e16..1ab6e636d 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,10 +43,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index bc96a7d05..5853b7de4 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index dc286c718..4fe96aa5e 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/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 8c8af3b5b..6597bf6d3 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -405,9 +405,7 @@ Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenize from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 110c5e1ab..5dc966bbd 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -413,9 +413,7 @@ Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTok from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -663,9 +661,7 @@ Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenC from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -774,10 +770,7 @@ D'abord nous devons construire le `DataLoader`s à partir de nos jeux de donnée from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -788,9 +781,7 @@ Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne cont ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index cfc6af14e..f992a1bb2 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -793,10 +793,7 @@ 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, + 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 diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index cfac55b89..9414e5a66 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -941,8 +941,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e2074354a..e93821f30 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1048,10 +1048,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 84f022ead..666894267 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..3690bcae5 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..3359ab062 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..b689c074d 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..008db7af8 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..a72f16354 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,9 +151,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..24718bd2d 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..1e7e91108 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,9 +132,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 2bf0ef5f8..bea755456 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` From 80b0fbdf4ca07b8d0d220d86e1b5a8e70aa40b45 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 30 May 2022 10:52:14 +0200 Subject: [PATCH 210/789] Fix notebook generation (#227) * Add Gradio nb links --- 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 | 7 +++ chapters/en/chapter9/7.mdx | 7 +++ utils/code_formatter.py | 30 +++++------ utils/generate_notebooks.py | 99 ++++++++++++++++++++++--------------- 8 files changed, 120 insertions(+), 59 deletions(-) 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 eab5e885e..28a8c17f4 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -1,5 +1,12 @@ # 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. ### 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/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..875797744 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,12 +55,13 @@ 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: return "".join(content) + def extract_cells(content): """ Extract the code/output cells from content. @@ -96,12 +98,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 +116,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 +156,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 +176,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 +193,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 +231,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 d9bef0c131f0b295fa17f22cd7286d6223c6a4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Mon, 30 May 2022 05:56:25 -0300 Subject: [PATCH 211/789] add 5.4 (#226) --- chapters/pt/_toctree.yml | 3 +- chapters/pt/chapter5/4.mdx | 287 +++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 chapters/pt/chapter5/4.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 02de3dd5f..69560b3c7 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -54,4 +54,5 @@ 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 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! From 4052957ae52b8335dfe683ab3111da4d9be21934 Mon Sep 17 00:00:00 2001 From: Lincoln V Schreiber Date: Mon, 30 May 2022 05:56:56 -0300 Subject: [PATCH 212/789] add pt wip (#225) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9f7590e27..04d8370c4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [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) | From feff8a3a7f919fd41b8a8c04060219c03af969a4 Mon Sep 17 00:00:00 2001 From: Vedant Pandya Date: Mon, 30 May 2022 14:30:21 +0530 Subject: [PATCH 213/789] Added Gujarati List. (#221) --- README.md | 2 + chapters/hi/_toctree.yml | 4 + chapters/hi/chapter1/3.mdx | 344 +++++++++++++++++++++++++++++++++++++ chapters/hi/chapter1/4.mdx | 166 ++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 chapters/hi/chapter1/3.mdx create mode 100644 chapters/hi/chapter1/4.mdx diff --git a/README.md b/README.md index 04d8370c4..9f46e8eef 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,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) | | [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) | @@ -18,6 +19,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [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/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 टीम द्वारा प्रशिक्षित वज़न का एक सेट एक चेकपॉइंट है। हालांकि, कोई "बीईआरटी मॉडल" और "`बर्ट-बेस-केसेड` मॉडल" कह सकता है। From 11ff5ec3f99b89e41df81e41060523fb0840ed26 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 30 May 2022 11:04:31 +0200 Subject: [PATCH 214/789] Fix quality --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 3 +- chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/fr/chapter3/3_tf.mdx | 3 +- chapters/fr/chapter5/4.mdx | 2 +- chapters/fr/chapter6/8.mdx | 4 +- chapters/fr/chapter7/2.mdx | 17 +- chapters/fr/chapter7/4.mdx | 5 +- chapters/fr/chapter7/5.mdx | 3 +- chapters/fr/chapter7/7.mdx | 5 +- chapters/hi/chapter3/3_tf.mdx | 3 +- chapters/it/chapter1/3.mdx | 4 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter3/3_tf.mdx | 377 ++++++++++++++++--------------- 29 files changed, 292 insertions(+), 222 deletions(-) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 566457a0e..dd1be7835 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index cd6aee466..ac22e7e8f 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index 313c1fc53..d1304d737 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 6d43884e4..2252a9613 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index b7d2609f7..cb90067f4 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index c7cef7308..301648c7e 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,7 +404,9 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 5a99e497c..9d1ccc3b9 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,7 +413,9 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -661,7 +663,9 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -770,7 +774,10 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -781,7 +788,9 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 2d599c3c7..5aa654ceb 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,7 +795,10 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index f344f7b10..1f9280c15 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -928,7 +928,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index 8e18ac50b..d8e1942e4 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,7 +1029,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index 04ac7f60a..c725bb68d 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 1ab6e636d..71abc5e16 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,7 +43,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 5853b7de4..bc96a7d05 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 4fe96aa5e..4c73d15c5 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -90,7 +90,7 @@ Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, q ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 6597bf6d3..8c8af3b5b 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -405,7 +405,9 @@ Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenize from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 5dc966bbd..110c5e1ab 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -413,7 +413,9 @@ Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTok from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -661,7 +663,9 @@ Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenC from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -770,7 +774,10 @@ D'abord nous devons construire le `DataLoader`s à partir de nos jeux de donnée from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -781,7 +788,9 @@ Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne cont ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index f992a1bb2..cfc6af14e 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -793,7 +793,10 @@ 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, + 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 diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 9414e5a66..cfac55b89 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -941,7 +941,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e93821f30..e2074354a 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1048,7 +1048,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 666894267..84f022ead 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 3690bcae5..7fb506a94 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index 3359ab062..f32892430 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index b689c074d..88c9a068e 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 008db7af8..2f28dc98a 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index a72f16354..9ab990db5 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,7 +151,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 24718bd2d..87968254b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 1e7e91108..076263ba4 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,7 +132,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index bea755456..2bf0ef5f8 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index ac98487f8..911e12a92 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -1,189 +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 微调一个模型 + + + +完成上一节中的所有数据预处理工作后,您只剩下最后的几个步骤来训练模型。 但是请注意,`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 From 1d9d9d390bb2b6a2c1ac4a8c658bf1537eba863d Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 30 May 2022 11:08:29 +0200 Subject: [PATCH 215/789] Add Gradio nbs links to fr (#228) --- 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 +++++++ 6 files changed, 42 insertions(+) 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` ? From d22cab9a52e3b1bef393875d9ec0e61a1b16c5d1 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 30 May 2022 11:10:18 +0200 Subject: [PATCH 216/789] Fix ToC tree --- chapters/en/_toctree.yml | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index 7cae8efbe..0ae46daa8 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -168,6 +168,7 @@ quiz: 8 - title: 9. Building and sharing demos + new: true subtitle: I trained a model, but how can I show it off? sections: - local: chapter9/1 @@ -190,27 +191,6 @@ title: End-of-chapter quiz quiz: 9 -- title: 10. Transformers can hear - new: true - sections: - - local: chapter10/1 - title: Introduction - - local: chapter10/2 - title: Why use Transformer for audio? - - local: chapter10/3 - title: Training your first audio Transformer - - local: chapter10/4 - title: Automatically recognizing speech - - local: chapter10/5 - title: Converting text to speech - - local: chapter10/6 - title: Audio to audio - - local: chapter10/7 - title: Audio transformers, check! - - local: chapter10/8 - title: End-of-chapter quiz - quiz: 10 - - title: Hugging Face Course Event sections: - local: event/1 From c930f3c068f27fc5635ad279cc0ae05e66a68193 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 30 May 2022 11:11:43 +0200 Subject: [PATCH 217/789] Remove audio templates --- chapters/en/chapter10/1.mdx | 14 --- chapters/en/chapter10/2.mdx | 7 -- chapters/en/chapter10/3.mdx | 3 - chapters/en/chapter10/4.mdx | 1 - chapters/en/chapter10/5.mdx | 1 - chapters/en/chapter10/6.mdx | 1 - chapters/en/chapter10/7.mdx | 1 - chapters/en/chapter10/8.mdx | 234 ------------------------------------ 8 files changed, 262 deletions(-) delete mode 100644 chapters/en/chapter10/1.mdx delete mode 100644 chapters/en/chapter10/2.mdx delete mode 100644 chapters/en/chapter10/3.mdx delete mode 100644 chapters/en/chapter10/4.mdx delete mode 100644 chapters/en/chapter10/5.mdx delete mode 100644 chapters/en/chapter10/6.mdx delete mode 100644 chapters/en/chapter10/7.mdx delete mode 100644 chapters/en/chapter10/8.mdx diff --git a/chapters/en/chapter10/1.mdx b/chapters/en/chapter10/1.mdx deleted file mode 100644 index 898965322..000000000 --- a/chapters/en/chapter10/1.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# Going beyond text - -So far in this course, we've seen that Transformers are exceptionally good at tackling a wide variety of tasks in NLP. But did you know that Transformers can also be applied to entirely different _modalities_ like audio and images? In this chapter, we will explore how Transformers can process _audio waveforms_ to produce novel applications like speech recognition. - -After explaining why Transformers might be suitable for audio, each of the sections in this chapter will dive into some of the most common tasks in this fast-moving field: - -* Classifying audio signals into a set of categories. -* Transcribing speech to text -* Converting text to speech -* Bypassing text altogether and converting audio to audio - -To do this, you'll need to leverage everything you learned about the `Trainer` API library in [Chapter 3](/course/chapter3), the 🤗 Datasets library in [Chapter 5](/course/chapter5), and the Gradio library in [Chapter 9](/course/chapter9). So go read those chapters first if you haven't done so already. - -Let's now dive into the fascinating world of audio! \ No newline at end of file diff --git a/chapters/en/chapter10/2.mdx b/chapters/en/chapter10/2.mdx deleted file mode 100644 index 089a55eb3..000000000 --- a/chapters/en/chapter10/2.mdx +++ /dev/null @@ -1,7 +0,0 @@ -# Why use Transformers for audio? - -Random ideas: - -* A brief bit of history on pre-transformer approaches? -* Mention popular architectures like wav2vec2, xsl-r etc -* Show pipelines for most common audio tasks? \ No newline at end of file diff --git a/chapters/en/chapter10/3.mdx b/chapters/en/chapter10/3.mdx deleted file mode 100644 index ad6b7b913..000000000 --- a/chapters/en/chapter10/3.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Training your first audio transformer - -Place holder for audio classification \ No newline at end of file diff --git a/chapters/en/chapter10/4.mdx b/chapters/en/chapter10/4.mdx deleted file mode 100644 index 9f47e6b32..000000000 --- a/chapters/en/chapter10/4.mdx +++ /dev/null @@ -1 +0,0 @@ -# Automatically recognizing speech \ No newline at end of file diff --git a/chapters/en/chapter10/5.mdx b/chapters/en/chapter10/5.mdx deleted file mode 100644 index 9d8fecef4..000000000 --- a/chapters/en/chapter10/5.mdx +++ /dev/null @@ -1 +0,0 @@ -# Converting text to speech \ No newline at end of file diff --git a/chapters/en/chapter10/6.mdx b/chapters/en/chapter10/6.mdx deleted file mode 100644 index 190c42e06..000000000 --- a/chapters/en/chapter10/6.mdx +++ /dev/null @@ -1 +0,0 @@ -# Audio to audio \ No newline at end of file diff --git a/chapters/en/chapter10/7.mdx b/chapters/en/chapter10/7.mdx deleted file mode 100644 index c9f10f2b8..000000000 --- a/chapters/en/chapter10/7.mdx +++ /dev/null @@ -1 +0,0 @@ -# Audio transformers, check! \ No newline at end of file diff --git a/chapters/en/chapter10/8.mdx b/chapters/en/chapter10/8.mdx deleted file mode 100644 index b5e73698b..000000000 --- a/chapters/en/chapter10/8.mdx +++ /dev/null @@ -1,234 +0,0 @@ - - -# 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 From ac4d1b1d0b1cff52d699e6100de2d8f721da7a38 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 30 May 2022 11:14:24 +0200 Subject: [PATCH 218/789] Fix fr section --- chapters/fr/chapter3/3_tf.mdx | 380 +++++++++++++++++----------------- 1 file changed, 190 insertions(+), 190 deletions(-) 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. From 7036ab1dd479e247b7017a0e7b5094111acf3985 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 30 May 2022 11:16:10 +0200 Subject: [PATCH 219/789] Fix fr chapter --- chapters/fr/chapter5/4.mdx | 592 ++++----- chapters/fr/chapter6/8.mdx | 1132 ++++++++--------- chapters/fr/chapter7/2.mdx | 1962 ++++++++++++++-------------- chapters/fr/chapter7/4.mdx | 1998 ++++++++++++++--------------- chapters/fr/chapter7/5.mdx | 2130 +++++++++++++++---------------- chapters/fr/chapter7/7.mdx | 2456 ++++++++++++++++++------------------ 6 files changed, 5135 insertions(+), 5135 deletions(-) diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 4c73d15c5..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 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/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/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 ! From 2302e4836648da80bdbc5b58a4fc5ac6a21152fd Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Mon, 30 May 2022 17:28:52 +0800 Subject: [PATCH 220/789] Chinese - Chapter 3finished (#219) --- 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 +++++++++++++++++++++++ 8 files changed, 1448 insertions(+), 3 deletions(-) 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/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 From 447c78929ccca77bc3f3730208e1be69059b1aa7 Mon Sep 17 00:00:00 2001 From: Lincoln V Schreiber Date: Mon, 30 May 2022 08:26:18 -0300 Subject: [PATCH 221/789] add ch7 at _toctree and translate 7.1 (#222) --- chapters/pt/_toctree.yml | 5 +++++ chapters/pt/chapter7/1.mdx | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 chapters/pt/chapter7/1.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 69560b3c7..7ba1a7cfa 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -56,3 +56,8 @@ 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/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. + + From 7cead81c2472d881b73265859c51a3ae4ea5dc32 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 30 May 2022 16:06:31 +0200 Subject: [PATCH 222/789] chapter title fixed --- chapters/fa/_toctree.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index d45d23d0e..4884167eb 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -19,6 +19,13 @@ - local: chapter2/3 title: مدل‌ها +- title: ۳. کوک کردن یک مدل از پیش تعلیم دیده # Translate this! + sections: + - local: chapter3/1 + title: مقدمه # Translate this! + - local: chapter3/2 + title: پردازش داده # Translate this! + - title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها sections: - local: chapter4/1 @@ -26,13 +33,6 @@ - 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 From 4fd7475da184dd87dbcf1140a5b73ff32025e17b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 30 May 2022 16:09:30 +0200 Subject: [PATCH 223/789] minor fix - chapter title --- chapters/fa/_toctree.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 5dcfcc83c..021a2c1eb 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -19,7 +19,7 @@ - local: chapter2/3 title: مدل‌ها -- title: ۳. کوک کردن یک مدل از پیش تعلیم دیده # Translate this! +- title: ۳- کوک کردن یک مدل از پیش تعلیم دیده # Translate this! sections: - local: chapter3/1 title: مقدمه # Translate this! From 25c3b71b9c7b235435d14cb7216bc29b2650a34a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 30 May 2022 16:12:01 +0200 Subject: [PATCH 224/789] fixe typos the title of chapter 3 --- chapters/fa/_toctree.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 021a2c1eb..7336e1e93 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -19,12 +19,12 @@ - local: chapter2/3 title: مدل‌ها -- title: ۳- کوک کردن یک مدل از پیش تعلیم دیده # Translate this! +- title: ۳- کوک کردن یک مدل از پیش تعلیم دیده sections: - local: chapter3/1 - title: مقدمه # Translate this! + title: مقدمه - local: chapter3/2 - title: پردازش داده # Translate this! + title: پردازش داده - title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها sections: From b66cdef68759f6fab0a08c2e37e5586c489c93df Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 10:04:27 +0200 Subject: [PATCH 225/789] new file added --- chapters/fa/chapter3/3_tf.mdx | 263 ++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 chapters/fa/chapter3/3_tf.mdx diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx new file mode 100644 index 000000000..6e5d2c746 --- /dev/null +++ b/chapters/fa/chapter3/3_tf.mdx @@ -0,0 +1,263 @@ + + +
+ +# کوک کردن مدل‌ها با استفاده از کِراس + +# Fine-tuning a model with Keras + + + +وقتی که شما همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPUها یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. + +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/). + +نمونه کدهای زیر فرض می‌کنند شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: + +The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: + +
+ +```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 models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. + + + +جمله بالا به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. + +That means that once we have our data, very little work is required to begin training on it. + + + +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: + +As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: + +
+ +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +
+ +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه-سر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه-سر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه-سر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. + +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. + +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای تعلیم و خطای تایید را در انتهای هر 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. + + + +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع خطای مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت برچسب‌ دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب دسته مجزا، که روش معمول استفاده از برچسب‌ دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع خطای استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. + +Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. + + + +
+ +```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, +) +``` + +
+ + + +Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. + + + +### Improving training performance + + + +If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause +is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes +that optimizer with default values for all parameters, including learning rate. From long experience, though, we know +that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written +as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. + +In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate +over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* +the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is +`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial +value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, +though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. + +
+ +```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) +``` + +
+ + + +The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. + + + +Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new 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"]) +``` + +
+ +Now, we fit again: + +
+ +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + +
+ + + +💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) + + + +### Model predictions + + + + +Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. + +
+ +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +
+ +We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: + +
+ +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +
+ +
+ +```python out +(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: + +
+ +```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} +``` + +
+ +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. + +
\ No newline at end of file From d1feca3c9b8548953f79f24dd968956f7b53aaad Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 15:29:18 +0200 Subject: [PATCH 226/789] new paragraphs added --- chapters/fa/chapter3/3_tf.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 6e5d2c746..4a351f66b 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -73,7 +73,7 @@ That means that once we have our data, very little work is required to begin tra -مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب‌دسته استفاده خواهیم کرد: As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: @@ -91,13 +91,13 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین خطای تعلیم و خطای تایید را در انتهای هر epoch گزارش می‌دهد. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه تعلیم و هزینه تایید را در انتهای هر 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. -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع خطای مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع خطا در زمان `compile()` تعیین نکنید آنها از این تابع خطا به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع خطای داخلی شما نیاز خواهید داشت برچسب‌ دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب دسته مجزا، که روش معمول استفاده از برچسب‌ دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع خطای درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع خطای استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب‌دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب‌دسته مجزا، که روش معمول استفاده از برچسب‌دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. @@ -123,6 +123,8 @@ model.fit( +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فرض می‌کند که شما از پیش یک لایه softmax به خروجی خودتان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود خروجی می‌دهند، که تحت عنوان *logits* نیز شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم که این چیزی است که مدل ما انجام می‌دهد و تنها راه انجام این کار این است که آن را به صورت مستقیم صدا بزنیم، به جای استفاده از یک نام به صورت یک متغییر رشته‌ای. + Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. From 644599a629f6af92e8b722e7ce7bcb0a7d52fbaa Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 15:42:27 +0200 Subject: [PATCH 227/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 4a351f66b..22587d302 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -91,13 +91,13 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه تعلیم و هزینه تایید را در انتهای هر epoch گزارش می‌دهد. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه تعلیم و هزینه تایید را در انتهای هر 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. -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب‌دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب‌دسته مجزا، که روش معمول استفاده از برچسب‌دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب‌دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب‌دسته مجزا، که روش معمول استفاده از برچسب‌دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. @@ -129,6 +129,8 @@ Note a very common pitfall here — you *can* just pass the name of the loss as +### بهبود کارایی آموزش + ### Improving training performance From 531f9634c197e8511fcfc1dae26dc7078f5610c4 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 15:48:25 +0200 Subject: [PATCH 228/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 22587d302..db2229019 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -123,7 +123,7 @@ model.fit( -در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فرض می‌کند که شما از پیش یک لایه softmax به خروجی خودتان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود خروجی می‌دهند، که تحت عنوان *logits* نیز شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم که این چیزی است که مدل ما انجام می‌دهد و تنها راه انجام این کار این است که آن را به صورت مستقیم صدا بزنیم، به جای استفاده از یک نام به صورت یک متغییر رشته‌ای. +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softma از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود خروجی می‌دهند، که تحت عنوان *logits* نیز شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم که این چیزی است که مدل ما انجام می‌دهد و تنها راه انجام این کار این است که آن را به صورت مستقیم صدا بزنیم، به جای استفاده از یک نام به صورت یک متغییر رشته‌ای. Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. From 99b95027dcaa7490e32083caed5f0e784dce955b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 15:54:06 +0200 Subject: [PATCH 229/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index db2229019..b31484642 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -123,7 +123,7 @@ model.fit( -در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softma از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود خروجی می‌دهند، که تحت عنوان *logits* نیز شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم که این چیزی است که مدل ما انجام می‌دهد و تنها راه انجام این کار این است که آن را به صورت مستقیم صدا بزنیم، به جای استفاده از یک نام به صورت یک متغییر رشته‌ای. +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود به خروجی می‌دهند، که به آنها *logits* نیز گفته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای استفاده از یک نام به صورت یک متغییر رشته‌ای، آن را به صورت مستقیم صدا بزنیم. Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. From 2f9e9b5c43025d1cb07397a55ec3da77f4fd66b0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 16:17:02 +0200 Subject: [PATCH 230/789] new paragraph added --- chapters/fa/chapter3/3_tf.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index b31484642..3998f4427 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -123,7 +123,7 @@ model.fit( -در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغییر رشته‌ای برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود به خروجی می‌دهند، که به آنها *logits* نیز گفته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای استفاده از یک نام به صورت یک متغییر رشته‌ای، آن را به صورت مستقیم صدا بزنیم. +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود به خروجی می‌دهند، که به آنها *logits* نیز گفته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. @@ -135,6 +135,8 @@ Note a very common pitfall here — you *can* just pass the name of the loss as +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن بهینه‌ساز، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. + If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes that optimizer with default values for all parameters, including learning rate. From long experience, though, we know From 9d89e11ac0de394437a4d3aba853ce7b87a03047 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 16:17:59 +0200 Subject: [PATCH 231/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 3998f4427..259835477 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -135,7 +135,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن بهینه‌ساز، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes From 9cdff871f5996a7faa2160a9136e1902a096b56b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Wed, 1 Jun 2022 16:42:48 +0200 Subject: [PATCH 232/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 259835477..1984ee463 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -135,7 +135,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes From 2ab6994b8b2f9125f2f692428eaa051bec2146eb Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 08:18:36 +0200 Subject: [PATCH 233/789] new paragraph added --- chapters/fa/chapter3/3_tf.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 1984ee463..999746a06 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -135,7 +135,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ تعلیم، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes @@ -143,6 +143,8 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. +علاوه بر کم کردن نرخ یادگیری، ما ترفند دیگری در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. در متون مشابه گاها خواهید دید که از این روش با عنوان نرخ یادگیری *محو شونده* یا *باز پختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمانبند نرخ یادگیری* است.* یک زمانبند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمانبند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانبند ما نیاز داریم که به آن بگوییم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. + In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is From 5b7ca84e56ffb53eb032246b711a11a07685df7d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 11:25:54 +0200 Subject: [PATCH 234/789] all paragraphs added --- chapters/fa/chapter3/3_tf.mdx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 999746a06..05e540c15 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -143,7 +143,7 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -علاوه بر کم کردن نرخ یادگیری، ما ترفند دیگری در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. در متون مشابه گاها خواهید دید که از این روش با عنوان نرخ یادگیری *محو شونده* یا *باز پختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمانبند نرخ یادگیری* است.* یک زمانبند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمانبند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانبند ما نیاز داریم که به آن بگوییم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن نرخ یادگیری، ما ترفند دیگری در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. در متون مشابه گاها خواهید دید که از این روش با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمانبند نرخ یادگیری* است.* یک زمانبند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمانبند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانبند ما نیاز داریم که به آن بگوییم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* @@ -175,10 +175,14 @@ opt = Adam(learning_rate=lr_scheduler) +کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که یک بهینه‌ساز `AdamW` دارای اندازه کاهش نرخ یادگیری می‌سازد. این یک میانبر مناسب است که آنرا با جزئیات در بخش‌های بعدی این آموزش خواهید دید. + The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. +اکنون بهینه ساز کاملا جدیدمان را در اختار داریم و می‌توانیم آنرا تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: + Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer:
@@ -205,14 +209,20 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در این‌باره در [Chapter 4](/course/chapter4/3) بیشتر خواهیم آموخت. + 💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) +# پیش‌بینی‌های مدل + ### Model predictions +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما چه کار کنیم اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها یا چه برای استفاده در خط تولید، خروجی دریافت کنیم؟ برای انجام این کار می‌توانیم از تابع `predict()` استفاده کنیم. این کار *logits* را از لایه سر خروجی مدل، یکی به ازای هر کلاس، باز می‌گرداند. + Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. @@ -224,6 +234,8 @@ preds = model.predict(tf_validation_dataset)["logits"]
+ما می‌توانیم logitها را با استفاده از `argmax` برای یافتن بزرگترین لاجیت، که نماینده محتمل ترین دسته می‌باشد به پیش‌بینی‌های دسته مدل تبدیل کنیم، + We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class:
@@ -243,6 +255,8 @@ print(preds.shape, class_preds.shape)
+اکنون، اجازه دهید از `preds` برای محاسبه برخی معیارها استفاده کنیم! ما می‌توانیم معیارهای مرتبط با دیتاسِت MRPC را، به همان آسانی که دیتاسِت را بارگذاری کردیم، بارگذاری کنیم اما این بار با استفاده از تابع `load_metric()`. شیء باز گردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه معیارها از آن استفاده کنیم: + 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:
@@ -264,8 +278,12 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
+از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه آزمون دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT paper](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. + 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. +به این ترتیب مقدمه بازتعلیم با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید خود را روی `API` کِراس کار آزموده کنید، سعی کنید مدلی را روی مسئله `GLUE SST-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. 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.
\ No newline at end of file From e4a6674103b4536d5e30c21a2dab9dfcf02badd1 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 11:53:49 +0200 Subject: [PATCH 235/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 05e540c15..934502c11 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -282,7 +282,7 @@ 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. -به این ترتیب مقدمه بازتعلیم با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید خود را روی `API` کِراس کار آزموده کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, بازتعلیم دهید +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید خود را روی `API` کِراس کار آزموده کنید، سعی کنید مدلی را روی مسئله `GLUE SST-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. 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. From 0e1e56f0e7458140b987496e83b34adb2be8a5bd Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 11:55:09 +0200 Subject: [PATCH 236/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 934502c11..183b5ff4b 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -278,7 +278,7 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
-از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه آزمون دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT paper](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه آزمون دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. 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. From ecdec2a248b52d7d83d9b97ef46f42a874b4322d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:00:46 +0200 Subject: [PATCH 237/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 183b5ff4b..7b576b0e9 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -91,7 +91,7 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه تعلیم و هزینه تایید را در انتهای هر epoch گزارش می‌دهد. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه `training` و هزینه `validation` را در انتهای هر 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. @@ -278,7 +278,7 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
-از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه آزمون دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. 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. From 28bc80663b79d67fcf11c21008e21e30ec1a8973 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:09:02 +0200 Subject: [PATCH 238/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 7b576b0e9..0d2d224a3 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -234,7 +234,7 @@ preds = model.predict(tf_validation_dataset)["logits"]
-ما می‌توانیم logitها را با استفاده از `argmax` برای یافتن بزرگترین لاجیت، که نماینده محتمل ترین دسته می‌باشد به پیش‌بینی‌های دسته مدل تبدیل کنیم، +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگترین `logit`، که نماینده محتمل ترین دسته می‌باشد به پیش‌بینی‌های دسته مدل تبدیل کنیم، We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: @@ -278,7 +278,7 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
-از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح میکند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. 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. From 6b361fd2a4e1fd97681b89c6c62b239e14494d08 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:16:38 +0200 Subject: [PATCH 239/789] minor paragraph fixes applied --- chapters/fa/chapter3/3_tf.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 0d2d224a3..ddb8b064a 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -135,7 +135,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3، یا 0.001 نیز نوشته میشود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3 یا 0.001 نیز نوشته می‌شود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes @@ -143,7 +143,7 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -علاوه بر کم کردن نرخ یادگیری، ما ترفند دیگری در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. در متون مشابه گاها خواهید دید که از این روش با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمانبند نرخ یادگیری* است.* یک زمانبند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمانبند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانبند ما نیاز داریم که به آن بگوییم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانب‌ند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* From 9ee05b2af30c7f7b20e402cd0ec320191f6501e6 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:18:52 +0200 Subject: [PATCH 240/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index ddb8b064a..a6ca0d872 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -129,7 +129,9 @@ Note a very common pitfall here — you *can* just pass the name of the loss as
-### بهبود کارایی آموزش + +### بهبود کارایی تعلیم + ### Improving training performance @@ -143,7 +145,7 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -علاوه بر کم کردن یکباره نرخ یادگیری ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمانب‌ند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* @@ -215,7 +217,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -# پیش‌بینی‌های مدل +### پیش‌بینی‌های مدل ### Model predictions From cfcda0ae5c77ee0d8727bcea8446292ccb065fcc Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:22:45 +0200 Subject: [PATCH 241/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index a6ca0d872..9990d1f61 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -87,7 +87,7 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab
-شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه-سر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه-سر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه-سر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه-سر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. @@ -223,7 +223,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما چه کار کنیم اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها یا چه برای استفاده در خط تولید، خروجی دریافت کنیم؟ برای انجام این کار می‌توانیم از تابع `predict()` استفاده کنیم. این کار *logits* را از لایه سر خروجی مدل، یکی به ازای هر کلاس، باز می‌گرداند. +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما چه کار کنیم اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها یا چه برای استفاده در خط تولید، خروجی دریافت کنیم؟ برای انجام این کار می‌توانیم از تابع `predict()` استفاده کنیم. این کار *logits* را از لایه‌ سَر خروجی مدل، یکی به ازای هر کلاس، باز می‌گرداند. Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. @@ -280,7 +280,7 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
-از آنجایی که مقداردهی اولیه تصادفی در لایه‌سر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. 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. From 17b37be11f6f7c7b4aabb26ffe8ba6d77f5c05aa Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:38:08 +0200 Subject: [PATCH 242/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 9990d1f61..ddaca838b 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -17,7 +17,7 @@ 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/). -نمونه کدهای زیر فرض می‌کنند شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: +نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: @@ -73,7 +73,7 @@ That means that once we have our data, very little work is required to begin tra -مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب‌دسته استفاده خواهیم کرد: +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: @@ -87,7 +87,7 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab -شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان چیزی است که می‌خواهیم اکنون انجام دهیم. +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. @@ -97,7 +97,7 @@ To fine-tune the model on our dataset, we just have to `compile()` our model and -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب‌دسته‌های خودتان را به عنوان بخشی از ورودی ارسال کنید، نه به صورت یک برچسب‌دسته مجزا، که روش معمول استفاده از برچسب‌دسته‌ها در مدل‌های کِراس می‌باشد. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب دسته‌های خودتان را به عنوان بخشی از ورودی، نه به صورت یک برچسب دسته مجزا که روش معمول استفاده از برچسب دسته‌ها در مدل‌های کِراس می‌باشد، ارسال کنید. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه‌ی درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. @@ -123,7 +123,7 @@ model.fit( -در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax اعمال شود به خروجی می‌دهند، که به آنها *logits* نیز گفته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax به آنها اعمال شود به خروجی می‌دهند، که همچنین به عنوان *logits* شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. From a34fb6d344cc2d322619e013882ddd516cbce2b1 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:45:14 +0200 Subject: [PATCH 243/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index ddaca838b..aed8bec16 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -137,7 +137,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و همچنین به صورت 10 به توان -3 یا 0.001 نیز نوشته می‌شود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که به صورت‌های 10 به توان -3 یا 0.001 نیز نوشته می‌شود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes @@ -145,7 +145,7 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -علاوه بر کم کردن یکباره نرخ یادگیری ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* @@ -177,13 +177,13 @@ opt = Adam(learning_rate=lr_scheduler) -کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که یک بهینه‌ساز `AdamW` دارای اندازه کاهش نرخ یادگیری می‌سازد. این یک میانبر مناسب است که آنرا با جزئیات در بخش‌های بعدی این آموزش خواهید دید. +کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW` دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. -اکنون بهینه ساز کاملا جدیدمان را در اختار داریم و می‌توانیم آنرا تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: +اکنون بهینه ساز کاملا جدیدمان را در اختار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer: From 268f7211876abf8faecae948161a91b4e70c2462 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:58:09 +0200 Subject: [PATCH 244/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index aed8bec16..980dbeaf7 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -137,7 +137,7 @@ Note a very common pitfall here — you *can* just pass the name of the loss as -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچکتر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که به صورت‌های 10 به توان -3 یا 0.001 نیز نوشته می‌شود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که و صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes @@ -145,7 +145,7 @@ that optimizer with default values for all parameters, including learning rate. that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی نیز یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* @@ -177,13 +177,13 @@ opt = Adam(learning_rate=lr_scheduler) -کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW` دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. +کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW`، دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. -اکنون بهینه ساز کاملا جدیدمان را در اختار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: +اکنون بهینه‌ساز کاملا جدیدمان را در اختیار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer: @@ -199,6 +199,8 @@ model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +حالا دوباره مدل را تعلیم می‌دهیم: + Now, we fit again:
@@ -211,7 +213,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در این‌باره در [Chapter 4](/course/chapter4/3) بیشتر خواهیم آموخت. +اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. 💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) @@ -223,7 +225,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما چه کار کنیم اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها یا چه برای استفاده در خط تولید، خروجی دریافت کنیم؟ برای انجام این کار می‌توانیم از تابع `predict()` استفاده کنیم. این کار *logits* را از لایه‌ سَر خروجی مدل، یکی به ازای هر کلاس، باز می‌گرداند. +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر دسته یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. From 56bf11959795e896fe66e982d085b4751a3987f7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 12:59:41 +0200 Subject: [PATCH 245/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 980dbeaf7..0ab11ef6c 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -225,7 +225,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر دسته یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. From b1c8c1d4e9320daa7842e06b8860476966331457 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:00:09 +0200 Subject: [PATCH 246/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 0ab11ef6c..ac74e527e 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -225,7 +225,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. From 4f974d5a1ea14cd5c8276f74cd455564b9b79f49 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:01:16 +0200 Subject: [PATCH 247/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index ac74e527e..e3d60b658 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -238,7 +238,7 @@ preds = model.predict(tf_validation_dataset)["logits"]
-سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگترین `logit`، که نماینده محتمل ترین دسته می‌باشد به پیش‌بینی‌های دسته مدل تبدیل کنیم، +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم، We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: From 64303ef6c2e96b5d20968680e6c5881b1bbbf938 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:01:59 +0200 Subject: [PATCH 248/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index e3d60b658..cafd7f59e 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -238,7 +238,7 @@ preds = model.predict(tf_validation_dataset)["logits"] -سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم، +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم، We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: From 67f1b0bb8836a85eaad785c7a22e99a337201583 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:04:43 +0200 Subject: [PATCH 249/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index cafd7f59e..91420a328 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -238,7 +238,7 @@ preds = model.predict(tf_validation_dataset)["logits"] -سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم، +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم: We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: @@ -282,7 +282,7 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l -از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. 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. From c0940eaae50098cdec49fc505b1f298fcbf91ddb Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:13:36 +0200 Subject: [PATCH 250/789] prune original text --- chapters/fa/chapter3/3_tf.mdx | 58 ++--------------------------------- 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 91420a328..0d7aea252 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -4,7 +4,6 @@ # کوک کردن مدل‌ها با استفاده از کِراس -# Fine-tuning a model with Keras `model.fit()`
روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPUها یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. -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/). - نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: -
```py @@ -63,20 +58,15 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( مدل‌های تِنسورفِلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. -TensorFlow models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. جمله بالا به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. -That means that once we have our data, very little work is required to begin training on it. - مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: -As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: -
```py @@ -89,18 +79,12 @@ model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_lab شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. - برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه `training` و هزینه `validation` را در انتهای هر 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. - توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب دسته‌های خودتان را به عنوان بخشی از ورودی، نه به صورت یک برچسب دسته مجزا که روش معمول استفاده از برچسب دسته‌ها در مدل‌های کِراس می‌باشد، ارسال کنید. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه‌ی درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. -Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. -
@@ -125,35 +109,17 @@ model.fit( در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax به آنها اعمال شود به خروجی می‌دهند، که همچنین به عنوان *logits* شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. -Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. - ### بهبود کارایی تعلیم - -### Improving training performance - اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که و صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. -If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause -is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes -that optimizer with default values for all parameters, including learning rate. From long experience, though, we know -that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written -as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. - علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. -In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate -over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* -the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is -`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial -value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, -though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. -
```py @@ -179,14 +145,10 @@ opt = Adam(learning_rate=lr_scheduler) کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW`، دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. -The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. - اکنون بهینه‌ساز کاملا جدیدمان را در اختیار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: -Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer: -
```py @@ -199,9 +161,7 @@ model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])
-حالا دوباره مدل را تعلیم می‌دهیم: - -Now, we fit again: +حالا دوباره مدل را فیت می‌کنیم:
@@ -213,23 +173,17 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. - -💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) +💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. ### پیش‌بینی‌های مدل -### Model predictions - تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. -Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. -
```py @@ -240,8 +194,6 @@ preds = model.predict(tf_validation_dataset)["logits"] سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم: -We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: -
```py @@ -261,8 +213,6 @@ print(preds.shape, class_preds.shape) اکنون، اجازه دهید از `preds` برای محاسبه برخی معیارها استفاده کنیم! ما می‌توانیم معیارهای مرتبط با دیتاسِت MRPC را، به همان آسانی که دیتاسِت را بارگذاری کردیم، بارگذاری کنیم اما این بار با استفاده از تابع `load_metric()`. شیء باز گردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه معیارها از آن استفاده کنیم: -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: -
```py @@ -284,10 +234,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. -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. - به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید خود را روی `API` کِراس کار آزموده کنید، سعی کنید مدلی را روی مسئله `GLUE SST-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. 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. -
\ No newline at end of file From 350986fb7a5a1130145aedfcb61c0aad7cf9c85a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:15:07 +0200 Subject: [PATCH 251/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 0d7aea252..bc7a4c0bb 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -234,6 +234,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید خود را روی `API` کِراس کار آزموده کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, کوک کنید. +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, کوک کنید.
\ No newline at end of file From 898295e6915c71b8e269bb497d34082ecf64b83c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:15:53 +0200 Subject: [PATCH 252/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index bc7a4c0bb..f5e1f706f 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -234,6 +234,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` در فصل ۷ ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, کوک کنید. +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, کوک کنید.
\ No newline at end of file From e2ee265bb828907ab889b9477417ccb3d1949a7b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:16:34 +0200 Subject: [PATCH 253/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index f5e1f706f..fe457123c 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -234,6 +234,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ای که در بخش ۲ انجام دادید, کوک کنید. +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید, کوک کنید.
\ No newline at end of file From 4fd6112ffb354db51667bea7d965a89537bf53ee Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:17:09 +0200 Subject: [PATCH 254/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index fe457123c..71d76fc47 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -234,6 +234,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`, با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید, کوک کنید. +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`، با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید، کوک کنید.
\ No newline at end of file From 611b3ca428a4087d7d9361bbf169ac5a75535f17 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:20:03 +0200 Subject: [PATCH 255/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 71d76fc47..c6e573ba7 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -12,7 +12,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter3/section3_tf.ipynb"}, ]} /> -وقتی که شما همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPUها یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPUها یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: From e7c671de7e02e3c9cd41055010a6dae390ac7af7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:21:19 +0200 Subject: [PATCH 256/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index c6e573ba7..982679336 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -12,7 +12,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter3/section3_tf.ipynb"}, ]} /> -زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPUها یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: From 541e0fdbb220b8fc780bf8200c7105f83a00cd84 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:23:02 +0200 Subject: [PATCH 257/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 982679336..0e2fafec3 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -61,7 +61,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( -جمله بالا به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. +این به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. From 42d214ad8335351514e9c9ed78f8829ab6f35b9a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:27:07 +0200 Subject: [PATCH 258/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 0e2fafec3..5a5a9f45f 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -116,7 +116,7 @@ model.fit( -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که و صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. From 49985766291d8dd03f8c3aceeaa0e0c725344cc5 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:27:56 +0200 Subject: [PATCH 259/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 5a5a9f45f..5cd98f554 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -116,7 +116,7 @@ model.fit( -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد که به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. From 41ecfb1c0e439c639b6605042d7cf7857cf31bfd Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:28:55 +0200 Subject: [PATCH 260/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 5cd98f554..76b0fe996 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -118,7 +118,7 @@ model.fit( اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. -علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم.
From 10c311684e0cca9c042f1f2fc8fd5911344ba493 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Fri, 3 Jun 2022 13:31:33 +0200 Subject: [PATCH 261/789] minor fix --- chapters/fa/chapter3/3_tf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 76b0fe996..9e1b2be95 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -181,7 +181,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ به برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند.
From 52b88830c9b18ac0ebaf89b99d124cca3f79bd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Thu, 9 Jun 2022 09:41:33 -0300 Subject: [PATCH 262/789] add 5.5 (#235) --- chapters/pt/_toctree.yml | 2 + chapters/pt/chapter5/5.mdx | 471 +++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 chapters/pt/chapter5/5.mdx 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`. + + + + From 7f5cc16a72a1a45f10d766b069498fb5162aab01 Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Thu, 9 Jun 2022 14:52:46 +0200 Subject: [PATCH 263/789] [FR] Review of chapter 7 (#233) --- 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 ++++++++--------- 9 files changed, 634 insertions(+), 613 deletions(-) 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." } ]} /> From 4c742591eb52f25f2084eb6b2728f0ed2cba6fcc Mon Sep 17 00:00:00 2001 From: Giorgio Severi Date: Thu, 9 Jun 2022 08:56:22 -0400 Subject: [PATCH 264/789] Italian translation - chapter 4 (#230) --- 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 +++++++++++++ 7 files changed, 1086 insertions(+) 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 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." + } + ]} +/> From 097edcb3912a7a8f315bdaaa2f8b4bbd02109a89 Mon Sep 17 00:00:00 2001 From: Suteera Seeha <33692408+meanna@users.noreply.github.com> Date: Thu, 9 Jun 2022 15:04:24 +0200 Subject: [PATCH 265/789] Added Thai translation of chapters 3 (#231) --- chapters/th/_toctree.yml | 2 + chapters/th/chapter6/3.mdx | 524 +++++++++++++++++++++++++++++++++++++ 2 files changed, 526 insertions(+) create mode 100644 chapters/th/chapter6/3.mdx 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 6dd7e6c1f9da29bc32794175822c52a365172033 Mon Sep 17 00:00:00 2001 From: svv73 <88366711+svv73@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:10:57 +0400 Subject: [PATCH 266/789] [Ru] Add part 2, chapter 2 (#234) --- chapters/ru/_toctree.yml | 10 +- chapters/ru/chapter2/2.mdx | 354 +++++++++++++++++++++++++++++++++++++ chapters/ru/chapter2/3.mdx | 227 ++++++++++++++++++++++++ chapters/ru/chapter2/7.mdx | 13 ++ 4 files changed, 601 insertions(+), 3 deletions(-) create mode 100644 chapters/ru/chapter2/2.mdx create mode 100644 chapters/ru/chapter2/3.mdx create mode 100644 chapters/ru/chapter2/7.mdx 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: словарный запас будет для вас знаком, и к тому уже вы видели методы, которые будете использовать большую часть времени. From 225c91d316126ddc13ca20524fe6ff8184eb2e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20=C3=96zdemir?= Date: Thu, 9 Jun 2022 18:00:33 +0300 Subject: [PATCH 267/789] Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section --- chapters/en/chapter9/8.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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. From ab1f91c50ee65531c6b57c887edd4b01a042f4f0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sat, 11 Jun 2022 18:16:20 +0200 Subject: [PATCH 268/789] new file added --- chapters/fa/chapter3/3.mdx | 176 +++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 chapters/fa/chapter3/3.mdx diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx new file mode 100644 index 000000000..5265697e2 --- /dev/null +++ b/chapters/fa/chapter3/3.mdx @@ -0,0 +1,176 @@ + + +
+ +# کوک کردن مدل‌ها با استفاده API تعلیم + +# Fine-tuning a model with the Trainer API + + + + + +🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). + +The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: + +```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 + +The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) + + + +The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. + +Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `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, +) +``` + +Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! + +To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`: + +```py +trainer.train() +``` + +This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: + +1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). +2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). + + +### Evaluation + +Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. + +As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: + +```py +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: + +```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} +``` + +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. + +Wrapping everything together, we get our `compute_metrics()` function: + +```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) +``` + +And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: + +```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, +) +``` + +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: + +``` +trainer.train() +``` + +This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. + +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. + + + +✏️ **Try it out!** Fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. + + + From 76da5dc8221c7d6f7d0b3fb4c0d9b5dba392805f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 10:39:20 +0200 Subject: [PATCH 269/789] new paragraph added --- chapters/fa/chapter3/3.mdx | 6 +++++- chapters/fa/chapter3/3_tf.mdx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 5265697e2..4ece6177a 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -2,7 +2,7 @@
-# کوک کردن مدل‌ها با استفاده API تعلیم +# کوک کردن مدل‌ها با استفاده از API تعلیم دهنده # Fine-tuning a model with the Trainer API @@ -15,8 +15,12 @@ +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود.اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. + 🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). +نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: + The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: ```py diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 9e1b2be95..d631cc545 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -12,7 +12,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter3/section3_tf.ipynb"}, ]} /> -زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قیمانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قی‌مانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: From c34e358c7acad82238e8f6045a48516ae4c643ed Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 10:41:10 +0200 Subject: [PATCH 270/789] right to left span --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 4ece6177a..f372f8b14 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -15,7 +15,7 @@ -ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود.اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود.اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. 🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). From 8112c47f3955eb42f2db2217a049318ddd15fcb7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 11:10:21 +0200 Subject: [PATCH 271/789] new paragraph added --- chapters/fa/chapter3/3.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index f372f8b14..c6dcecd1d 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -15,7 +15,7 @@ -ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود.اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. 🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). @@ -40,8 +40,12 @@ tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` +### تعلیم + ### Training +قبل از این که بتوانیم `Trainer` مان را تعریف کنیم اولین مرحله تعریف کلاس `TrainingArguments` می‌باشد که شامل همه پارامترهای سطح بالایی است که `Trainer` برای `Training` و `Evaluation` استفاده خواهد کرد. تنها آرگومانی که شما باید ارائه کنید آدرسی است که مدل تعلیم دیده به همراه نفاط تعلیم در آن ذخیره خواهند شد. بقیه پارامترها را می‌توانید به حالت پیش‌فرض رها کنید، که برای کوک کردن پایه به خوبی کار خواهد کرد. + The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. ```py @@ -52,10 +56,14 @@ training_args = TrainingArguments("test-trainer") +اگر مایلید مدل‌تان را در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. + 💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) +مرحله دوم تعریف مدل‌مان می‌باشد. مانند [فصل قبل](/course/chapter2)، از کلاس `AutoModelForSequenceClassification` با دو برچسب کلاس استفاده خواهیم کرد: + The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels: ```py From 6e5541763b98650bc1d48baa6a20e36fa47a3e36 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 11:16:44 +0200 Subject: [PATCH 272/789] left to right code sections fixed --- chapters/fa/chapter3/3.mdx | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index c6dcecd1d..af01f33c3 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -23,6 +23,8 @@ The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: +
+ ```py from datasets import load_dataset from transformers import AutoTokenizer, DataCollatorWithPadding @@ -40,6 +42,8 @@ tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` +
+ ### تعلیم ### Training @@ -48,12 +52,16 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. +
+ ```py from transformers import TrainingArguments training_args = TrainingArguments("test-trainer") ``` +
+ اگر مایلید مدل‌تان را در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. @@ -66,16 +74,22 @@ training_args = TrainingArguments("test-trainer") The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels: +
+ ```py from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` +
+ You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: +
+ ```py from transformers import Trainer @@ -89,14 +103,20 @@ trainer = Trainer( ) ``` +
+ Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`: +
+ ```py trainer.train() ``` +
+ This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: 1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). @@ -107,6 +127,8 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: +
+ ```py predictions = trainer.predict(tokenized_datasets["validation"]) print(predictions.predictions.shape, predictions.label_ids.shape) @@ -116,18 +138,26 @@ print(predictions.predictions.shape, predictions.label_ids.shape) (408, 2) (408,) ``` +
+ The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: +
+ ```py 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: +
+ ```py from datasets import load_metric @@ -139,10 +169,14 @@ metric.compute(predictions=preds, references=predictions.label_ids) {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` +
+ 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. Wrapping everything together, we get our `compute_metrics()` function: +
+ ```py def compute_metrics(eval_preds): metric = load_metric("glue", "mrpc") @@ -151,8 +185,12 @@ def compute_metrics(eval_preds): return metric.compute(predictions=predictions, references=labels) ``` +
+ And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: +
+ ```py training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) @@ -168,12 +206,18 @@ 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: +
+ ``` trainer.train() ``` +
+ This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. 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. From b5f9674376ccee15f33e9bdf7955fd58ac0703e0 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 17:39:32 +0200 Subject: [PATCH 273/789] two new paragraphs added --- chapters/fa/chapter3/3.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index af01f33c3..9e85d59ed 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -84,8 +84,13 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label
+ +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. + You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، training دیتاسِت‌های validation و `data_collator`مان `training_args` و توکِنایزرمان به آن تعریف کنیم: + Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`:
From 33e599477184054277b282d790bf694a6478f2b3 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 17:44:33 +0200 Subject: [PATCH 274/789] minor sentence structure fixed --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 9e85d59ed..65cd3843e 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، training دیتاسِت‌های validation و `data_collator`مان `training_args` و توکِنایزرمان به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator و توکِنایزرمان به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 6480710ca8c4d7286454ec736c2efbc627835e2c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 17:46:17 +0200 Subject: [PATCH 275/789] minor sentence structure fixed --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 65cd3843e..56cc6ca10 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator و توکِنایزرمان به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و توکِنایزرمان به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 631312fe19e30b136edab38ed7b26bb8ee929f35 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Mon, 13 Jun 2022 19:44:05 +0200 Subject: [PATCH 276/789] new paragraphs added --- chapters/fa/chapter3/3.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 56cc6ca10..f585ef1ca 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -110,8 +110,12 @@ trainer = Trainer(
+توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، همانطور که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer` `DataCollatorWithPadding` خواهد بود، همانطور که قبلا تعریف کردیم، پس شما می‌توانید خط `data_collator=data_collator` را در این صدا کردن نادیده بگیرید. + Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! +برای کوک کردن مدل روی دیتاسِت‌مان ما فقط باید تابع `train()` از `Trainer`مان را صدا بزنیم: + To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`:
From 85df4d93617b6005ff64bc272ae6e2f1e0003440 Mon Sep 17 00:00:00 2001 From: Vedant Pandya Date: Tue, 14 Jun 2022 20:53:03 +0530 Subject: [PATCH 277/789] Created HI/Chapter1/5.mdx (#232) --- 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 ++ 7 files changed, 353 insertions(+) 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 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 | संक्षिप्तीकरण, अनुवाद, प्रश्न उत्तर बनाना | From 04eac7793aff1aa07e52d829f3835275f1e0e66b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:16:07 +0200 Subject: [PATCH 278/789] new paragraph added --- chapters/fa/chapter3/3.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index f585ef1ca..97263239a 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -126,8 +126,13 @@ trainer.train()
+این کار کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و خطای تعلیم را هر ۵۰۰ مرحله یکبار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: + This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با تنظیم پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) +۲. ما تابع `compute_metrics()` را جهت محاسبه معیارها در حین اصطلاحا ارزیابی (که در غیر این صورت فقط هزینه، که عدد چندان گویایی هم نیست، را چاپ می‌کرد) برای `Trainer` فراهم نکردیم. + 1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). 2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). From 0cd3a2f1dbb98fd9b7b81b743d0a6ff5559a1458 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:19:42 +0200 Subject: [PATCH 279/789] fixed typose --- chapters/fa/chapter3/3.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 97263239a..3960428f6 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -126,11 +126,12 @@ trainer.train()
-این کار کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و خطای تعلیم را هر ۵۰۰ مرحله یکبار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: +این کار کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و هزینه تعلیم را هر ۵۰۰ مرحله یکبار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: -۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با تنظیم پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با تنظیم پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) انجام دهیم. + ۲. ما تابع `compute_metrics()` را جهت محاسبه معیارها در حین اصطلاحا ارزیابی (که در غیر این صورت فقط هزینه، که عدد چندان گویایی هم نیست، را چاپ می‌کرد) برای `Trainer` فراهم نکردیم. 1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). From 2e60166a86da6c665d50eec44ec4764566c09435 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:28:52 +0200 Subject: [PATCH 280/789] new paragraph added --- chapters/fa/chapter3/3.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 3960428f6..7ad5ca092 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -130,7 +130,7 @@ trainer.train() This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: -۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با تنظیم پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) انجام دهیم. +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) انجام دهیم. ۲. ما تابع `compute_metrics()` را جهت محاسبه معیارها در حین اصطلاحا ارزیابی (که در غیر این صورت فقط هزینه، که عدد چندان گویایی هم نیست، را چاپ می‌کرد) برای `Trainer` فراهم نکردیم. @@ -138,8 +138,12 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) 2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). +### ارزیابی + ### Evaluation +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids` و دیکشنری باز می‌گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). + Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command:
From 272dcccda470cf7119720831cc804883f4cff0db Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:29:37 +0200 Subject: [PATCH 281/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 7ad5ca092..2fe856fd3 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -142,7 +142,7 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) ### Evaluation -اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids` و دیکشنری باز می‌گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids` و دیکشنری باز می‌گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: From e5389cc183b00ff3336dbe1eed50f9b1d161e684 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:31:02 +0200 Subject: [PATCH 282/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 2fe856fd3..e4b979802 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -142,7 +142,7 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) ### Evaluation -اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids` و دیکشنری باز می‌گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: From cfe895878adaa2b7ed2afb69dd19a2b925535084 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:33:45 +0200 Subject: [PATCH 283/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index e4b979802..1f12e86c2 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -142,7 +142,7 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) ### Evaluation -اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). برای استخراج چند پیش‌بینی‌ از مدل‌مان، می‌توانیم از دستور `Trainer.predict()` استفاده کنیم: Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: From 0e614ca0258a04e633f3bdf4e4f14ff4b8833633 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 14 Jun 2022 19:40:28 +0200 Subject: [PATCH 284/789] new paragraph added --- chapters/fa/chapter3/3.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 1f12e86c2..aaf335232 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -159,6 +159,8 @@ print(predictions.predictions.shape, predictions.label_ids.shape)
+خروجی تابع --- تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشید) می‌باشد. + The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From e216c2f7c3f4c43d3fd4182ea72c2214f86f1fe0 Mon Sep 17 00:00:00 2001 From: Fermin Ordaz Date: Thu, 16 Jun 2022 23:20:46 -0700 Subject: [PATCH 285/789] Add Spanish chaper3/section4, update toc and glossary (#238) --- chapters/es/_toctree.yml | 6 +- chapters/es/chapter3/4.mdx | 357 +++++++++++++++++++++++++++++++++++++ chapters/es/glossary/1.mdx | 141 ++++++++------- 3 files changed, 433 insertions(+), 71 deletions(-) create mode 100644 chapters/es/chapter3/4.mdx 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 From 77b3e15cd44200d33b927483503d317751b654d4 Mon Sep 17 00:00:00 2001 From: Pavel <60391448+pdumin@users.noreply.github.com> Date: Fri, 17 Jun 2022 09:23:11 +0300 Subject: [PATCH 286/789] [RU] Chapter 3 finished (#239) --- 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 +++++++++++ 9 files changed, 644 insertions(+), 10 deletions(-) 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 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} + + + +При использовании предварительно обученной модели обязательно проверьте: как она была обучена, на каких наборах данных, ее ограничениях и смещениях. Вся эта информация должна быть указана в карточке модели. + From 3681c5047e6d0cd6ccc87bbeb5e10737ba5279b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Fri, 17 Jun 2022 03:24:26 -0300 Subject: [PATCH 287/789] [PT] add 5.6 and 5.7 (#240) --- chapters/pt/_toctree.yml | 4 + chapters/pt/chapter5/6.mdx | 529 +++++++++++++++++++++++++++++++++++++ chapters/pt/chapter5/7.mdx | 11 + 3 files changed, 544 insertions(+) create mode 100644 chapters/pt/chapter5/6.mdx create mode 100644 chapters/pt/chapter5/7.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 3c5b11bea..9ca971c79 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -58,6 +58,10 @@ 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! - title: 7. Principais tarefas NLP sections: 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 From 9385a66d0965fcc737b3a7da7fa7880265551f4f Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Fri, 17 Jun 2022 08:49:37 +0200 Subject: [PATCH 288/789] [EN] Visual corrections (#245) --- chapters/en/chapter7/5.mdx | 2102 ++++++++++++++++++------------------ chapters/en/chapter9/3.mdx | 370 +++---- chapters/en/chapter9/9.mdx | 466 ++++---- chapters/fr/chapter9/8.mdx | 9 +- 4 files changed, 1474 insertions(+), 1473 deletions(-) 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..5f2ee7dd9 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,186 +1,186 @@ -# 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. - -## How to create an Interface - -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"`. - - `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 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. - -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. -So let's build an `Interface` that works with audio. - -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, -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 -"Speak here...". - -In addition, we'd like to receive the audio as a numpy array so that we can easily -"reverse" it. So we'll set the `"type"` to be `"numpy"`, which passes the input -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 -shortcut `"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() -``` - -The code above will produce an interface like the one below (if your browser doesn't -ask you for microphone permissions, 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 - -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. - -The key here is that when you pass: -* a list of input components, each component corresponds to a parameter in order. -* a list of output coponents, each component corresponds to a returned value. - -The code snippet below shows how three input components line up with the three arguments of the `generate_tone()` function: - -```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() -``` - - - - -### The `launch()` method - -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 -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. - -You can customize the behavior of `launch()` through different parameters: - - - `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! - -We'll cover the `share` parameter in a lot more detail in the next section! - -## ✏️ Let's 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. - -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 -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() -``` - -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 -by passing in the `optional` parameter as `True`, we allow the user to either -provide a microphone or an audio file (or neither, but that will return an error message). - +# 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. + +## How to create an Interface + +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"`. + - `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 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. + +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. +So let's build an `Interface` that works with audio. + +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, +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 +"Speak here...". + +In addition, we'd like to receive the audio as a numpy array so that we can easily +"reverse" it. So we'll set the `"type"` to be `"numpy"`, which passes the input +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 +shortcut `"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() +``` + +The code above will produce an interface like the one below (if your browser doesn't +ask you for microphone permissions, 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 + +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. + +The key here is that when you pass: +* a list of input components, each component corresponds to a parameter in order. +* a list of output coponents, each component corresponds to a returned value. + +The code snippet below shows how three input components line up with the three arguments of the `generate_tone()` function: + +```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() +``` + + + + +### The `launch()` method + +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 +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. + +You can customize the behavior of `launch()` through different parameters: + + - `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! + +We'll cover the `share` parameter in a lot more detail in the next section! + +## ✏️ Let's 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. + +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 +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() +``` + +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 +by passing in the `optional` parameter as `True`, we allow the user to either +provide a microphone or an audio file (or neither, but that will return an error message). + Keep going to see how to share your interface with others! \ No newline at end of file diff --git a/chapters/en/chapter9/9.mdx b/chapters/en/chapter9/9.mdx index b5e73698b..30009fb54 100644 --- a/chapters/en/chapter9/9.mdx +++ b/chapters/en/chapter9/9.mdx @@ -1,234 +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. - - + +# End-of-chapter quiz + +Let's test what you learned in this chapter! + +### 1. What can you use Gradio to do? + +share=True parameter in the launch method, you can generate a share link to send to anyone.", + correct: true + }, + { + text: "Debug your model", + explain: "One advantage of a gradio demo is being able to test your model with real data which you can change and observe the model's predictions change in real time, helping you debug your model.", + correct: true + }, + { + text: "Train your model", + explain: "Gradio is designed to be used for model inference, AFTER your model is trained.", + } + ]} +/> + +### 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? + +gr.Interface.load() method", + correct: true + } + ]} +/> + +### 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/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx index 3ee33a1fb..d9898c938 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 From 4b9dab4c2479a6c4d78f0f7f95ca33f68a2f8f5d Mon Sep 17 00:00:00 2001 From: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Date: Fri, 17 Jun 2022 07:56:00 +0100 Subject: [PATCH 289/789] Translation for 1/4, 1/5 and 1/6. (#247) --- chapters/it/_toctree.yml | 9 +- chapters/it/chapter1/4.mdx | 171 +++++++++++++++++++++++++++++++++++++ chapters/it/chapter1/5.mdx | 17 ++++ chapters/it/chapter1/6.mdx | 16 ++++ 4 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 chapters/it/chapter1/4.mdx create mode 100644 chapters/it/chapter1/5.mdx create mode 100644 chapters/it/chapter1/6.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 6198bb32b..a08938643 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 @@ -27,4 +33,3 @@ - local: chapter4/6 title: Quiz di fine capitolo quiz: 4 - 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) From e20a7660853901b7f58dfb7fac4e030b725e3a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Fri, 17 Jun 2022 03:56:53 -0300 Subject: [PATCH 290/789] add event in PT (#250) --- chapters/pt/_toctree.yml | 6 ++ chapters/pt/event/1.mdx | 165 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 chapters/pt/event/1.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 9ca971c79..0bb7da9f9 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -67,3 +67,9 @@ 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/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. From b4dad8abb49b1b00c22afe36da9d47a592319de7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 12:48:41 +0200 Subject: [PATCH 291/789] new paragraphs added --- chapters/fa/chapter3/3.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index aaf335232..dc5ce898e 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -159,10 +159,12 @@ print(predictions.predictions.shape, predictions.label_ids.shape)
-خروجی تابع --- تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشید) می‌باشد. +خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آنرا به `Trainer` ارسال کردیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مرود استفاده‌مان می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: + As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis:
@@ -175,6 +177,8 @@ preds = np.argmax(predictions.predictions, axis=-1)
+اکنون می‌توانیم آن `preds`ها را با برچسب‌ها مقایسه کنیم. برای ساختن تابع `compute_metric()`مان، به متریک‌های کتابخانه داده‌های هاگینگ‌فِیس تکیه خواهیم کرد. ما می‌توانیم متریک های وابسته به دیتاسِت MRPC را به همان راحتی خود دیتاسِت بارگذاری کنیم، این بار با استفاده از تابع `load_metric()`. شیء بازگردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه متریک از آن استفاده کنیم: + 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:
From 8ff8d2d82fc4ad9039eccd08787752abba4d2f45 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 12:56:43 +0200 Subject: [PATCH 292/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index dc5ce898e..dc9c1086f 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -177,7 +177,7 @@ preds = np.argmax(predictions.predictions, axis=-1)
-اکنون می‌توانیم آن `preds`ها را با برچسب‌ها مقایسه کنیم. برای ساختن تابع `compute_metric()`مان، به متریک‌های کتابخانه داده‌های هاگینگ‌فِیس تکیه خواهیم کرد. ما می‌توانیم متریک های وابسته به دیتاسِت MRPC را به همان راحتی خود دیتاسِت بارگذاری کنیم، این بار با استفاده از تابع `load_metric()`. شیء بازگردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه متریک از آن استفاده کنیم: +اکنون می‌توانیم `preds` را با برچسب‌ها مقایسه کنیم. برای ساختن تابع `compute_metric()`، به متریک‌های کتابخانه داده‌های هاگینگ‌فِیس تکیه خواهیم کرد. ما می‌توانیم متریک‌های وابسته به دیتاسِت MRPC را به راحتی خود دیتاسِت، اما این بار با استفاده از تابع `load_metric()`، بارگذاری کنیم. شیء بازگردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه متریک از آن استفاده کنیم: 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: From 832d5c554d0ef3554311f9de814fba50b7766029 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 13:01:16 +0200 Subject: [PATCH 293/789] fixed typose --- chapters/fa/chapter3/3.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index dc9c1086f..91957bc35 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -159,11 +159,11 @@ print(predictions.predictions.shape, predictions.label_ids.shape)
-خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آنرا به `Trainer` ارسال کردیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. +خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آنرا به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مرود استفاده‌مان می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مرود استفاده‌مان می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From f4883d80e47a8a2f9966165584037c9c5c211f04 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 13:04:14 +0200 Subject: [PATCH 294/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 91957bc35..523d62968 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -163,7 +163,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مرود استفاده‌مان می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From c38c6296aa7ec7f98bfff355a5bcc4844012e008 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 13:05:06 +0200 Subject: [PATCH 295/789] fixed typose --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 523d62968..c0f513cb8 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -163,7 +163,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هر عنصر دیتاسِتی هستند که به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From c43a5c12987ae64338dd67818d65790ea70ccc29 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 13:06:40 +0200 Subject: [PATCH 296/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index c0f513cb8..b3c26e49c 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -163,7 +163,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌ها که بتوانیم با برچسب‌هایمان مقایسه کنیم، ما لازم داریم که اندیس مقدار بیشینه رود محور دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی محور دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From 08fa6b8d6e4a8d998dcc1545706219e1ab6b9b26 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Sun, 19 Jun 2022 13:07:50 +0200 Subject: [PATCH 297/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index b3c26e49c..7b943d1ed 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -163,7 +163,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی محور دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: From 44690a3e2a54d9ceedb42ee4f8230b7b8ae68b41 Mon Sep 17 00:00:00 2001 From: Hiromu Hota Date: Sun, 19 Jun 2022 13:03:05 -0700 Subject: [PATCH 298/789] Pin version of black (#252) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 16efa440e876fd78db5c2dbfe24e8a23d4748859 Mon Sep 17 00:00:00 2001 From: trtd56 <5toda6@gmail.com> Date: Mon, 20 Jun 2022 23:15:42 +0900 Subject: [PATCH 299/789] Translate ja event (#241) --- chapters/ja/_toctree.yml | 7 +- chapters/ja/event/1.mdx | 204 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 chapters/ja/event/1.mdx diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index ce0970fad..e7fef47c0 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -6,4 +6,9 @@ - title: 1. Transformerモデルについて sections: - local: chapter1/1 - title: イントロダクション \ No newline at end of file + title: イントロダクション + +- title: Hugging Faceコースのイベント + sections: + - local: event/1 + title: パート2公開記念イベント \ No newline at end of file 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チームとの協業をリードしています。 +最先端の自然言語処理モデルを誰もが使えるように製品化し、ディープラーニングの使いやすさを向上することに情熱を注いでいます。 From 456c608649fc6071d064c366a3fe9e3de38caa1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Mon, 20 Jun 2022 11:17:12 -0300 Subject: [PATCH 300/789] [PT] add quiz chapter 5 (#243) --- chapters/pt/_toctree.yml | 3 + chapters/pt/chapter5/8.mdx | 223 +++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 chapters/pt/chapter5/8.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 0bb7da9f9..168333305 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -62,6 +62,9 @@ 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: 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 + }, + ]} +/> From 01231a10ba957b93f2466e8ded1c6d5b6a130fee Mon Sep 17 00:00:00 2001 From: Mehrdad Nezamdoost Date: Mon, 20 Jun 2022 16:27:48 +0200 Subject: [PATCH 301/789] Update 5.mdx (#253) inconsistent naming with line 327 --- chapters/en/chapter5/5.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 3d5c36d2e5e05a0a23ff21706dafd5167502d664 Mon Sep 17 00:00:00 2001 From: Wolvz Date: Mon, 20 Jun 2022 07:34:43 -0700 Subject: [PATCH 302/789] Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 4 +- chapters/zh-TW/_toctree.yml | 4 + chapters/zh-TW/chapter0/1.mdx | 111 +++++++++++++++++++ upcoming_chapters/en/chapter10.md | 22 ---- upcoming_chapters/en/chapter11.md | 16 --- upcoming_chapters/en/chapter12.md | 23 ---- upcoming_chapters/en/chapter9.md | 24 ---- 8 files changed, 118 insertions(+), 88 deletions(-) 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/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/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 4ebc0b9a4512a934cfe1da4a9480179c79a09400 Mon Sep 17 00:00:00 2001 From: a-krirk <56425947+a-krirk@users.noreply.github.com> Date: Mon, 20 Jun 2022 21:40:22 +0700 Subject: [PATCH 303/789] Translated the whole Chapter 3 to Thai (#255) --- 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 ++++++++++++++++++++++++++ 7 files changed, 1437 insertions(+) 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 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 From bd16058873412bf18fe9b9ebd5ade516925289a6 Mon Sep 17 00:00:00 2001 From: Hiromu Hota Date: Tue, 21 Jun 2022 00:06:53 -0700 Subject: [PATCH 304/789] Japanese chapter 4 (#244) --- chapters/ja/_toctree.yml | 18 +- 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 +++++++++++++ 7 files changed, 1077 insertions(+), 1 deletion(-) 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 diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index e7fef47c0..0c72444af 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -8,7 +8,23 @@ - local: chapter1/1 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公開記念イベント \ No newline at end of file + 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を通してのマージは、未来永劫絶対にできません。" + } + ]} +/> From 66ef657d196f1286f1d367b92dace840bcc4baad Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 21 Jun 2022 13:05:18 +0200 Subject: [PATCH 305/789] new paragraphs added --- chapters/fa/chapter3/3.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 7b943d1ed..99a67cba0 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -196,8 +196,12 @@ metric.compute(predictions=preds, references=predictions.label_ids)
+از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا میتوانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` دارد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در [مقاله BERT](https://arxiv.org/pdf/1810.04805.pdf) `F1 Score` معادل ۸۸.۹ را برای مدل پایه گزارش می‌کند. آن مدل `uncased` بود، حال اینکه ما در اینجا از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. + 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. +با قرار دادن همه چیز کنارهم تابع `compute_metrics()` را بدست خواهیم آورد: + Wrapping everything together, we get our `compute_metrics()` function:
@@ -212,6 +216,8 @@ def compute_metrics(eval_preds):
+و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم، که ببینیم چگونه در عمل برای گزارش متریک‌های در پایان هر epoch مورد استفاده قرار می‌گیرد: + And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function:
From 5fa39b32d1a7184e7fe3b1534154671f5ce90c16 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 21 Jun 2022 13:11:42 +0200 Subject: [PATCH 306/789] fixed wording --- chapters/fa/chapter3/3.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 99a67cba0..f39c7d3a5 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -196,11 +196,11 @@ metric.compute(predictions=preds, references=predictions.label_ids)
-از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا میتوانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` دارد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در [مقاله BERT](https://arxiv.org/pdf/1810.04805.pdf) `F1 Score` معادل ۸۸.۹ را برای مدل پایه گزارش می‌کند. آن مدل `uncased` بود، حال اینکه ما در اینجا از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. +از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf) `F1 Score` معادل ۸۸.۹ را برای مدل پایه گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. 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. -با قرار دادن همه چیز کنارهم تابع `compute_metrics()` را بدست خواهیم آورد: +اکنون با قرار دادن همه چیز کنارهم تابع `compute_metrics()` را بدست خواهیم آورد: Wrapping everything together, we get our `compute_metrics()` function: @@ -216,7 +216,7 @@ def compute_metrics(eval_preds):
-و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم، که ببینیم چگونه در عمل برای گزارش متریک‌های در پایان هر epoch مورد استفاده قرار می‌گیرد: +و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم تا عملکرد آنرا در حین گزارش متریک‌ها در پایان هر epoch مشاهده کنیم: And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: From 6a432e4383061e8cc027cc1f696aacf11b780d3f Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Tue, 21 Jun 2022 13:15:23 +0200 Subject: [PATCH 307/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index f39c7d3a5..f6147f94e 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -196,7 +196,7 @@ metric.compute(predictions=preds, references=predictions.label_ids)
-از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf) `F1 Score` معادل ۸۸.۹ را برای مدل پایه گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. +از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. 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. @@ -216,7 +216,7 @@ def compute_metrics(eval_preds):
-و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم تا عملکرد آنرا در حین گزارش متریک‌ها در پایان هر epoch مشاهده کنیم: +و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم، تا بتوانیم عملکرد آن را در حین گزارش متریک‌ها در پایان هر epoch مشاهده کنیم: And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: From df6e86f318eb75b7c0ad3807e4591ee1cf7ee893 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:41:18 +0200 Subject: [PATCH 308/789] new paragraphs added --- chapters/fa/chapter3/3.mdx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index f6147f94e..694d64762 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -2,7 +2,7 @@
-# کوک کردن مدل‌ها با استفاده از API تعلیم دهنده +# کوک کردن مدل‌ها با استفاده از API `Trainer` # Fine-tuning a model with the Trainer API @@ -239,6 +239,8 @@ trainer = Trainer(
+توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` ست شده است می‌سازیم - در غیر این صورت فقط تعلیم مدلی را ادامه می‌دادیم که از تعلیم دیده بود. دستور زیر را برای راه‌اندازی دور جدید تعلیم اجرا می‌کنیم: + 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:
@@ -249,14 +251,22 @@ trainer.train()
+این بار هزینه validation و متریک‌ها را در پایان هر epoch و در بالای هزینه تعلیم گزارش می‌کنیم. دوباره، به خاطر مقدار دهی تصادفی اولیه لایه سر مدل، مقادیر دقیق accuracy/F1 score که شما بدست می‌آورید ممکن است کمی متفاوت از آنچه ما بدست آورده‌ایم باشد، اما این مقادیر باید در محدوده تخمینی یکسانی باشند. + This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. +`Trainer` به صورت پیش فرض روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. + 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. +این پایان مقدمه‌ای بر کوک کردن با استفاده از `Trainer` API می‌باشد. در [فصل ۷](/course/chapter7) مثالی برای نشان دادن چگونگی انجام این کار برای معمول‌ترین مسئله‌های NLP ارائه خواهیم کرد، اما اکنون اجازه دهید ببینیم چگونه همین کار را صرفا با استفاده از 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. +✏️ **اتحان کنید!** با استفاده از پردازش داده‌ای که در بخش ۲ انجام دادی، مدلی را روی دیتاسِت GLUE SST-2 کوک کنید، + ✏️ **Try it out!** Fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. From 83bb189100dbaebd405037fe8a4a597e31d1c139 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:45:05 +0200 Subject: [PATCH 309/789] fixed sentece wording and structure --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 694d64762..ad6f7747d 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -15,7 +15,7 @@ -ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌تواند باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت، احتمالا آماده‌سازی محیط جهت اجراي `Trainer.train()` باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. 🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). From b0d38c0aaaad0969d7c3a7980bfdc30e279b610d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:51:54 +0200 Subject: [PATCH 310/789] fixed sentece wording and structure --- chapters/fa/chapter3/3.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index ad6f7747d..926521fd3 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -15,7 +15,7 @@ -ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت، احتمالا آماده‌سازی محیط جهت اجراي `Trainer.train()` باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت، احتمالا آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. 🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). @@ -48,7 +48,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ### Training -قبل از این که بتوانیم `Trainer` مان را تعریف کنیم اولین مرحله تعریف کلاس `TrainingArguments` می‌باشد که شامل همه پارامترهای سطح بالایی است که `Trainer` برای `Training` و `Evaluation` استفاده خواهد کرد. تنها آرگومانی که شما باید ارائه کنید آدرسی است که مدل تعلیم دیده به همراه نفاط تعلیم در آن ذخیره خواهند شد. بقیه پارامترها را می‌توانید به حالت پیش‌فرض رها کنید، که برای کوک کردن پایه به خوبی کار خواهد کرد. +قبل از این که بتوانیم `Trainer` مان را تعریف کنیم اولین مرحله تعریف کلاس `TrainingArguments` می‌باشد که شامل همه پارامترهای سطح بالایی است که `Trainer` برای `Training` و `Evaluation` استفاده خواهد کرد. تنها آرگومانی که شما باید ارائه کنید آدرسی است که مدل تعلیم دیده به همراه نقاط تعلیم در آن ذخیره خواهند شد. بقیه پارامترها را می‌توانید به حالت پیش‌فرض رها کنید، که برای کوک کردن پایه به خوبی کار خواهد کرد. The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning. @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و توکِنایزرمان به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و توکِنایزرمان به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 92e0e5ccdba5050474ed1621c999336a6a630b00 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:53:44 +0200 Subject: [PATCH 311/789] fixed sentece wording and structure --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 926521fd3..3de86e7d0 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و توکِنایزرمان به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From e130d699017107cb250d5a49ad2f5b50f77a54c9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:55:17 +0200 Subject: [PATCH 312/789] fixed sentece wording and structure --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 3de86e7d0..4fc57e4ef 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`, `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 4fa5e7e6cdfd92a5cf0b921e1a2a7bb8fb164d0d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 09:58:02 +0200 Subject: [PATCH 313/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 4fc57e4ef..63725cf2b 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`, `data_collator` و `tokenizer` به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 410c43dd2c058eb934aafdd6916223e8f5e7f050 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:00:31 +0200 Subject: [PATCH 314/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 63725cf2b..b016309f8 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند شامل `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From fef307f91f10f789080dfbabc4d5ec2ff45c1d05 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:03:39 +0200 Subject: [PATCH 315/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index b016309f8..857851566 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `training_args`، `model`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 8ff9f08469ca5827d03b3aa44746ba9f5a375365 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:04:43 +0200 Subject: [PATCH 316/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 857851566..a0513d9eb 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به آن تعریف کنیم: +با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `validation` و `training`، `data_collator` و `tokenizer` به آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 4ca6047aeb55a3c37147546b285da85826d2369b Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:07:28 +0200 Subject: [PATCH 317/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index a0513d9eb..8c2504aaf 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -با داشتن مدل، ما می‌توانیم `Trainer`ای را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `validation` و `training`، `data_collator` و `tokenizer` به آن تعریف کنیم: +به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `validation` و `training`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 860ed2800d74b085d0f67a8836db40d434442f42 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:09:00 +0200 Subject: [PATCH 318/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 8c2504aaf..5f16244ef 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -89,7 +89,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `validation` و `training`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: +به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: From 87ec5754c044362d79baad9bd8c1bbd93bba8bd9 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:11:40 +0200 Subject: [PATCH 319/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 5f16244ef..c5e878c4a 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -110,7 +110,7 @@ trainer = Trainer( -توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، همانطور که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer` `DataCollatorWithPadding` خواهد بود، همانطور که قبلا تعریف کردیم، پس شما می‌توانید خط `data_collator=data_collator` را در این صدا کردن نادیده بگیرید. +توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، همانطور که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer` `DataCollatorWithPadding` خواهد بود، همانطور که قبلا تعریف کردیم، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! From a5729aa3c9fdef6376dbcdbecfd5c51986d3b3c7 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:16:12 +0200 Subject: [PATCH 320/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index c5e878c4a..7800043d0 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -110,7 +110,7 @@ trainer = Trainer( -توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، همانطور که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer` `DataCollatorWithPadding` خواهد بود، همانطور که قبلا تعریف کردیم، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. +توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، مثل کاری که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer`، همانطور که قبلا تعریف کردیم، `DataCollatorWithPadding` خواهد بود، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! From dfe8a8d19a3b641e802a1c402936919842a41e90 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:18:49 +0200 Subject: [PATCH 321/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 7800043d0..9000da2bf 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -130,7 +130,7 @@ trainer.train() This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: -۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر ایپاک) انجام دهیم. +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر epoch) انجام دهیم. ۲. ما تابع `compute_metrics()` را جهت محاسبه معیارها در حین اصطلاحا ارزیابی (که در غیر این صورت فقط هزینه، که عدد چندان گویایی هم نیست، را چاپ می‌کرد) برای `Trainer` فراهم نکردیم. From 9ab029ba4e75333a624e8ec8bd2fa17d97f54366 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:44:17 +0200 Subject: [PATCH 322/789] fixed wording --- chapters/fa/chapter3/3.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 9000da2bf..e4f4067ad 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -126,13 +126,13 @@ trainer.train() -این کار کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و هزینه تعلیم را هر ۵۰۰ مرحله یکبار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: +این کار، کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و هزینه تعلیم را هر ۵۰۰ مرحله یک‌بار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: -۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (جهت ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر epoch) انجام دهیم. +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (برای ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر epoch) انجام دهیم. -۲. ما تابع `compute_metrics()` را جهت محاسبه معیارها در حین اصطلاحا ارزیابی (که در غیر این صورت فقط هزینه، که عدد چندان گویایی هم نیست، را چاپ می‌کرد) برای `Trainer` فراهم نکردیم. +۲. ما تابع `compute_metrics()` را برای `Trainer` فراهم نکردیم تا بتواند معیارها را در حین اصطلاحا ارزیابی محاسبه کند (که در غیر این صورت، ارزیابی فقط هزینه را چاپ می‌کند که عدد چندان گویایی هم نیست) . 1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). 2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). From 372d8c5e774d3d6d000eb55a4ce8b1efd981dd49 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:48:03 +0200 Subject: [PATCH 323/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index e4f4067ad..34483b46f 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -142,7 +142,7 @@ This will start the fine-tuning (which should take a couple of minutes on a GPU) ### Evaluation -اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (که رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آنها می باشند). برای استخراج چند پیش‌بینی‌ از مدل‌مان، می‌توانیم از دستور `Trainer.predict()` استفاده کنیم: +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و یک دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آن‌ها می باشند). برای استخراج چند پیش‌بینی‌ از مدل‌مان، می‌توانیم از دستور `Trainer.predict()` استفاده کنیم: Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: From 9fafe844a7b71d2202a30ba018d859b49406392a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:55:19 +0200 Subject: [PATCH 324/789] fixed typo --- chapters/fa/chapter3/3.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 34483b46f..c41b896e3 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -159,11 +159,11 @@ print(predictions.predictions.shape, predictions.label_ids.shape) -خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (در مجموع و به طور میانگین پیش‌بینی‌ چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آنرا به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. +خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (پیش‌بینی‌، در مجموع و به طور میانگین، چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آن را به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل آنها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل لاجیت‌ها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: @@ -196,7 +196,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) -از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. +از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه، `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. 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. From dec3de9b6ad95f4c573090fb431cb787f1b1fc20 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:56:11 +0200 Subject: [PATCH 325/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index c41b896e3..a361bd759 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -196,7 +196,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) -از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه، `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال این که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. +از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه، `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال آن که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. 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. From 119853cd7831e5d6f17c2da56148a598e39b4a38 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:58:18 +0200 Subject: [PATCH 326/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index a361bd759..4434b9aa7 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -239,7 +239,7 @@ trainer = Trainer( -توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` ست شده است می‌سازیم - در غیر این صورت فقط تعلیم مدلی را ادامه می‌دادیم که از تعلیم دیده بود. دستور زیر را برای راه‌اندازی دور جدید تعلیم اجرا می‌کنیم: +توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` است می‌سازیم - در غیر این صورت فقط تعلیم مدلی که از پیش تعلیم دیده بود را ادامه می‌دادیم. دستور زیر را برای راه‌اندازی دور جدید تعلیم اجرا می‌کنیم: 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: From f4a5b552150846dac0291dabdef69c2f3725a02e Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 10:59:07 +0200 Subject: [PATCH 327/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 4434b9aa7..2f26edd24 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -239,7 +239,7 @@ trainer = Trainer( -توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` است می‌سازیم - در غیر این صورت فقط تعلیم مدلی که از پیش تعلیم دیده بود را ادامه می‌دادیم. دستور زیر را برای راه‌اندازی دور جدید تعلیم اجرا می‌کنیم: +توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` است می‌سازیم - در غیر این صورت فقط تعلیم مدلی که از پیش تعلیم دیده بود را ادامه می‌دادیم. برای راه‌اندازی دور جدید تعلیم، دستور زیر را اجرا می‌کنیم: 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: From f45102967290bd18252ab382519fdcaaeb278c6d Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:00:42 +0200 Subject: [PATCH 328/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 2f26edd24..2b6ffcb9f 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -251,7 +251,7 @@ trainer.train() -این بار هزینه validation و متریک‌ها را در پایان هر epoch و در بالای هزینه تعلیم گزارش می‌کنیم. دوباره، به خاطر مقدار دهی تصادفی اولیه لایه سر مدل، مقادیر دقیق accuracy/F1 score که شما بدست می‌آورید ممکن است کمی متفاوت از آنچه ما بدست آورده‌ایم باشد، اما این مقادیر باید در محدوده تخمینی یکسانی باشند. +این بار هزینه validation و متریک‌ها را در پایان هر epoch و در بالای هزینه تعلیم گزارش می‌کنیم. دوباره، به خاطر مقدار دهی تصادفی اولیه لایه سر مدل، مقادیر دقیق accuracy/F1 score که شما بدست می‌آورید ممکن است کمی متفاوت از آنچه ما بدست آورده‌ایم باشد، اما این مقادیر باید در محدوده تخمینی یکسانی باشند. This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. From 1b611d8f80bb05c35feb38a4f3f690bedf062f4c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:02:30 +0200 Subject: [PATCH 329/789] fixed ltr --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 2b6ffcb9f..0108d519a 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -255,7 +255,7 @@ trainer.train() This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. -`Trainer` به صورت پیش فرض روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. +به صورت پیش فرض `Trainer` روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. 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. From b79139bb40f77878585095becd9fe0ac1c014964 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:02:58 +0200 Subject: [PATCH 330/789] fixed wording --- chapters/fa/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 0108d519a..a59d19b09 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -255,7 +255,7 @@ trainer.train() This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. -به صورت پیش فرض `Trainer` روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. +به صورت پیش فرض، `Trainer` روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. 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. From d8eba670bc648e9c4e1b3e0a12e86f397efecaac Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:08:14 +0200 Subject: [PATCH 331/789] missing icon added --- chapters/fa/chapter3/3.mdx | 47 +++----------------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index a59d19b09..296c18bf5 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -64,7 +64,7 @@ training_args = TrainingArguments("test-trainer") -اگر مایلید مدل‌تان را در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. +💡 اگر مایلید مدل‌تان را در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. 💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) @@ -72,7 +72,6 @@ training_args = TrainingArguments("test-trainer") مرحله دوم تعریف مدل‌مان می‌باشد. مانند [فصل قبل](/course/chapter2)، از کلاس `AutoModelForSequenceClassification` با دو برچسب کلاس استفاده خواهیم کرد: -The second step is to define our model. As in the [previous chapter](/course/chapter2), we will use the `AutoModelForSequenceClassification` class, with two labels:
@@ -87,12 +86,8 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. - به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: -Once we have our model, we can define a `Trainer` by passing it all the objects constructed up to now — the `model`, the `training_args`, the training and validation datasets, our `data_collator`, and our `tokenizer`: -
```py @@ -110,14 +105,10 @@ trainer = Trainer(
-توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، مثل کاری که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer`، همانطور که قبلا تعریف کردیم، `DataCollatorWithPadding` خواهد بود، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. - -Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! +توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، مثل کاری که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer`، همانطور که قبلا تعریف کردیم، `DataCollatorWithPadding` خواهد بود، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. این هنوز مهم بود که این بخش از پردازش را در بخش ۲ به شما نشان دهیم! برای کوک کردن مدل روی دیتاسِت‌مان ما فقط باید تابع `train()` از `Trainer`مان را صدا بزنیم: -To fine-tune the model on our dataset, we just have to call the `train()` method of our `Trainer`: -
```py @@ -128,24 +119,14 @@ trainer.train() این کار، کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و هزینه تعلیم را هر ۵۰۰ مرحله یک‌بار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: -This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps. It won't, however, tell you how well (or badly) your model is performing. This is because: - ۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (برای ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر epoch) انجام دهیم. ۲. ما تابع `compute_metrics()` را برای `Trainer` فراهم نکردیم تا بتواند معیارها را در حین اصطلاحا ارزیابی محاسبه کند (که در غیر این صورت، ارزیابی فقط هزینه را چاپ می‌کند که عدد چندان گویایی هم نیست) . -1. We didn't tell the `Trainer` to evaluate during training by setting `evaluation_strategy` to either `"steps"` (evaluate every `eval_steps`) or `"epoch"` (evaluate at the end of each epoch). -2. We didn't provide the `Trainer` with a `compute_metrics()` function to calculate a metric during said evaluation (otherwise the evaluation would just have printed the loss, which is not a very intuitive number). - - ### ارزیابی -### Evaluation - اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و یک دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آن‌ها می باشند). برای استخراج چند پیش‌بینی‌ از مدل‌مان، می‌توانیم از دستور `Trainer.predict()` استفاده کنیم: -Let's see how we can build a useful `compute_metrics()` function and use it the next time we train. The function must take an `EvalPrediction` object (which is a named tuple with a `predictions` field and a `label_ids` field) and will return a dictionary mapping strings to floats (the strings being the names of the metrics returned, and the floats their values). To get some predictions from our model, we can use the `Trainer.predict()` command: -
```py @@ -161,12 +142,8 @@ print(predictions.predictions.shape, predictions.label_ids.shape) خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (پیش‌بینی‌، در مجموع و به طور میانگین، چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آن را به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. -The output of the `predict()` method is another named tuple with three fields: `predictions`, `label_ids`, and `metrics`. The `metrics` field will just contain the loss on the dataset passed, as well as some time metrics (how long it took to predict, in total and on average). Once we complete our `compute_metrics()` function and pass it to the `Trainer`, that field will also contain the metrics returned by `compute_metrics()`. - همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل لاجیت‌ها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: -As you can see, `predictions` is a two-dimensional array with shape 408 x 2 (408 being the number of elements in the dataset we used). Those are the logits for each element of the dataset we passed to `predict()` (as you saw in the [previous chapter](/course/chapter2), all Transformer models return logits). To transform them into predictions that we can compare to our labels, we need to take the index with the maximum value on the second axis: -
```py @@ -179,8 +156,6 @@ preds = np.argmax(predictions.predictions, axis=-1) اکنون می‌توانیم `preds` را با برچسب‌ها مقایسه کنیم. برای ساختن تابع `compute_metric()`، به متریک‌های کتابخانه داده‌های هاگینگ‌فِیس تکیه خواهیم کرد. ما می‌توانیم متریک‌های وابسته به دیتاسِت MRPC را به راحتی خود دیتاسِت، اما این بار با استفاده از تابع `load_metric()`، بارگذاری کنیم. شیء بازگردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه متریک از آن استفاده کنیم: -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: -
```py @@ -198,12 +173,8 @@ metric.compute(predictions=preds, references=predictions.label_ids) از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه، `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال آن که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. -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. - اکنون با قرار دادن همه چیز کنارهم تابع `compute_metrics()` را بدست خواهیم آورد: -Wrapping everything together, we get our `compute_metrics()` function: -
```py @@ -218,8 +189,6 @@ def compute_metrics(eval_preds): و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم، تا بتوانیم عملکرد آن را در حین گزارش متریک‌ها در پایان هر epoch مشاهده کنیم: -And to see it used in action to report metrics at the end of each epoch, here is how we define a new `Trainer` with this `compute_metrics()` function: -
```py @@ -241,8 +210,6 @@ trainer = Trainer( توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` است می‌سازیم - در غیر این صورت فقط تعلیم مدلی که از پیش تعلیم دیده بود را ادامه می‌دادیم. برای راه‌اندازی دور جدید تعلیم، دستور زیر را اجرا می‌کنیم: -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: -
``` @@ -253,21 +220,13 @@ trainer.train() این بار هزینه validation و متریک‌ها را در پایان هر epoch و در بالای هزینه تعلیم گزارش می‌کنیم. دوباره، به خاطر مقدار دهی تصادفی اولیه لایه سر مدل، مقادیر دقیق accuracy/F1 score که شما بدست می‌آورید ممکن است کمی متفاوت از آنچه ما بدست آورده‌ایم باشد، اما این مقادیر باید در محدوده تخمینی یکسانی باشند. -This time, it will report the validation loss and metrics at the end of each epoch on top of the training loss. Again, the exact accuracy/F1 score you reach might be a bit different from what we found, because of the random head initialization of the model, but it should be in the same ballpark. - به صورت پیش فرض، `Trainer` روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. -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. - این پایان مقدمه‌ای بر کوک کردن با استفاده از `Trainer` API می‌باشد. در [فصل ۷](/course/chapter7) مثالی برای نشان دادن چگونگی انجام این کار برای معمول‌ترین مسئله‌های NLP ارائه خواهیم کرد، اما اکنون اجازه دهید ببینیم چگونه همین کار را صرفا با استفاده از 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. - -✏️ **اتحان کنید!** با استفاده از پردازش داده‌ای که در بخش ۲ انجام دادی، مدلی را روی دیتاسِت GLUE SST-2 کوک کنید، - -✏️ **Try it out!** Fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. +✏️ **اتحان کنید!** با استفاده از پردازش داده‌ای که در بخش ۲ انجام دادید، مدلی را روی دیتاسِت GLUE SST-2 کوک کنید. From 8858f2c77361ffc55f0f3b4ca2ee4f08238c8867 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:10:15 +0200 Subject: [PATCH 332/789] remove original english text --- chapters/fa/chapter3/3.mdx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 296c18bf5..0e2b50626 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -4,8 +4,6 @@ # کوک کردن مدل‌ها با استفاده از API `Trainer` -# Fine-tuning a model with the Trainer API - `Trainer.train()` می‌باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. -🤗 Transformers provides a `Trainer` class to help you fine-tune any of the pretrained models it provides on your dataset. Once you've done all the data preprocessing work in the last section, you have just a few steps left to define the `Trainer`. The hardest part is likely to be preparing the environment to run `Trainer.train()`, as it 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/). - نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need:
@@ -46,11 +41,8 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ### تعلیم -### Training - قبل از این که بتوانیم `Trainer` مان را تعریف کنیم اولین مرحله تعریف کلاس `TrainingArguments` می‌باشد که شامل همه پارامترهای سطح بالایی است که `Trainer` برای `Training` و `Evaluation` استفاده خواهد کرد. تنها آرگومانی که شما باید ارائه کنید آدرسی است که مدل تعلیم دیده به همراه نقاط تعلیم در آن ذخیره خواهند شد. بقیه پارامترها را می‌توانید به حالت پیش‌فرض رها کنید، که برای کوک کردن پایه به خوبی کار خواهد کرد. -The first step before we can define our `Trainer` is to define a `TrainingArguments` class that will contain all the hyperparameters the `Trainer` will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning.
@@ -64,9 +56,7 @@ training_args = TrainingArguments("test-trainer") -💡 اگر مایلید مدل‌تان را در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. - -💡 If you want to automatically upload your model to the Hub during training, pass along `push_to_hub=True` in the `TrainingArguments`. We will learn more about this in [Chapter 4](/course/chapter4/3) +💡 اگر مایلید مدل‌تان را به صورت خودکار در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. From 9b9a72cebc0ef0b2ca294c0b0afdca7ddac0495c Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 11:16:59 +0200 Subject: [PATCH 333/789] final commit CH3 P3 --- chapters/fa/_toctree.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 7336e1e93..5b7f5151f 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -25,6 +25,9 @@ title: مقدمه - local: chapter3/2 title: پردازش داده + - local: chapter3/3 + title: کوک کردن مدل‌ها با استفاده از Trainer API یا کِراس + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها sections: @@ -33,13 +36,6 @@ - local: chapter4/2 title: بکارگیری مدل‌های از پیش تعلیم دیده -- title: 3. کوک کردن یک مدل از پیش تعلیم دیده - sections: - - local: chapter3/1 - title: مقدمه - - local: chapter3/2 - title: پردازش داده - - title: واژه‌نامه sections: - local: glossary/1 From f92b070aefb559b55327cfc1d924bb39903d1fa4 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 12:40:35 +0200 Subject: [PATCH 334/789] formatting fixed - section ending was missing --- chapters/fa/chapter3/3.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 0e2b50626..aa3928b75 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -220,3 +220,4 @@ trainer.train() +
\ No newline at end of file From 00e876ee7032dca06c0d921b2bf705224195d81a Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 12:47:06 +0200 Subject: [PATCH 335/789] fixed formatting --- chapters/fa/chapter3/3.mdx | 2 -- chapters/fa/chapter3/3_tf.mdx | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index aa3928b75..7880d346a 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -62,7 +62,6 @@ training_args = TrainingArguments("test-trainer") مرحله دوم تعریف مدل‌مان می‌باشد. مانند [فصل قبل](/course/chapter2)، از کلاس `AutoModelForSequenceClassification` با دو برچسب کلاس استفاده خواهیم کرد: -
```py @@ -73,7 +72,6 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_label
- شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index d631cc545..1bed20661 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -4,7 +4,6 @@ # کوک کردن مدل‌ها با استفاده از کِراس - این به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. @@ -111,7 +109,6 @@ model.fit( - ### بهبود کارایی تعلیم @@ -171,6 +168,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)
+ 💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. From 8d30cdd97a9c4af9ffcb331d914405f1cd6c9387 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 12:58:42 +0200 Subject: [PATCH 336/789] test 3_tf for formatting --- chapters/fa/chapter3/3_tf.mdx | 108 +++++++++++----------------------- 1 file changed, 35 insertions(+), 73 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 1bed20661..2252a9613 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -1,8 +1,6 @@ -
- -# کوک کردن مدل‌ها با استفاده از کِراس +# Fine-tuning a model with Keras -زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قی‌مانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. - -نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: +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/). -
+The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: ```py from datasets import load_dataset @@ -30,6 +26,7 @@ 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") @@ -51,21 +48,17 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -
- -### تعلیم +### Training -مدل‌های تِنسورفِلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. +TensorFlow models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. -این به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. +That means that once we have our data, very little work is required to begin training on it. -مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: - -
+As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: ```py from transformers import TFAutoModelForSequenceClassification @@ -73,20 +66,16 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -
- -شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. +You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. -برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه `training` و هزینه `validation` را در انتهای هر 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. -توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب دسته‌های خودتان را به عنوان بخشی از ورودی، نه به صورت یک برچسب دسته مجزا که روش معمول استفاده از برچسب دسته‌ها در مدل‌های کِراس می‌باشد، ارسال کنید. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه‌ی درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. +Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. -
- ```py from tensorflow.keras.losses import SparseCategoricalCrossentropy @@ -101,23 +90,29 @@ model.fit( ) ``` -
- -در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax به آنها اعمال شود به خروجی می‌دهند، که همچنین به عنوان *logits* شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. +Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. -### بهبود کارایی تعلیم - +### Improving training performance -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. + -علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause +is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes +that optimizer with default values for all parameters, including learning rate. From long experience, though, we know +that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written +as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. -
+In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate +over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* +the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is +`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial +value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, +though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. ```py from tensorflow.keras.optimizers.schedules import PolynomialDecay @@ -136,17 +131,13 @@ from tensorflow.keras.optimizers import Adam opt = Adam(learning_rate=lr_scheduler) ``` -
- -کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW`، دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. +The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. -اکنون بهینه‌ساز کاملا جدیدمان را در اختیار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: - -
+Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer: ```py import tensorflow as tf @@ -156,62 +147,41 @@ loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) ``` -
- -حالا دوباره مدل را فیت می‌کنیم: - -
+Now, we fit again: ```py model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) ``` -
- - -💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. +💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) -### پیش‌بینی‌های مدل +### Model predictions -تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. - -
+Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. ```py preds = model.predict(tf_validation_dataset)["logits"] ``` -
- -سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم: - -
+We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: ```py class_preds = np.argmax(preds, axis=1) print(preds.shape, class_preds.shape) ``` -
- -
- ```python out (408, 2) (408,) ``` -
- -اکنون، اجازه دهید از `preds` برای محاسبه برخی معیارها استفاده کنیم! ما می‌توانیم معیارهای مرتبط با دیتاسِت MRPC را، به همان آسانی که دیتاسِت را بارگذاری کردیم، بارگذاری کنیم اما این بار با استفاده از تابع `load_metric()`. شیء باز گردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه معیارها از آن استفاده کنیم: - -
+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: ```py from datasets import load_metric @@ -220,18 +190,10 @@ 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 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. - -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`، با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید، کوک کنید. +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. -
\ No newline at end of file +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. From 14f0b6cc15f2a6c78eb7954013a04fa839838db1 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 13:02:48 +0200 Subject: [PATCH 337/789] test 3_tf for formatting - put back --- chapters/fa/chapter3/3_tf.mdx | 108 +++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 2252a9613..1bed20661 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -1,6 +1,8 @@ -# Fine-tuning a model with Keras +
+ +# کوک کردن مدل‌ها با استفاده از کِراس -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/). +زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قی‌مانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. + +نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: -The code examples below assume you have already executed the examples in the previous section. Here is a short summary recapping what you need: +
```py from datasets import load_dataset @@ -26,7 +30,6 @@ 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") @@ -48,17 +51,21 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -### Training +
+ +### تعلیم -TensorFlow models imported from 🤗 Transformers are already Keras models. Here is a short introduction to Keras. +مدل‌های تِنسورفِلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. -That means that once we have our data, very little work is required to begin training on it. +این به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. -As in the [previous chapter](/course/chapter2), we will use the `TFAutoModelForSequenceClassification` class, with two labels: +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: + +
```py from transformers import TFAutoModelForSequenceClassification @@ -66,16 +73,20 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -You will notice that unlike in [Chapter 2](/course/chapter2), you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been inserted instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now. +
+ +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. -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. +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه `training` و هزینه `validation` را در انتهای هر epoch گزارش می‌دهد. -Note that 🤗 Transformers models have a special ability that most Keras models don't - they can automatically use an appropriate loss which they compute internally. They will use this loss by default if you don't set a loss argument in `compile()`. Note that to use the internal loss you'll need to pass your labels as part of the input, not as a separate label, which is the normal way to use labels with Keras models. You'll see examples of this in Part 2 of the course, where defining the correct loss function can be tricky. For sequence classification, however, a standard Keras loss function works fine, so that's what we'll use here. +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب دسته‌های خودتان را به عنوان بخشی از ورودی، نه به صورت یک برچسب دسته مجزا که روش معمول استفاده از برچسب دسته‌ها در مدل‌های کِراس می‌باشد، ارسال کنید. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه‌ی درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. +
+ ```py from tensorflow.keras.losses import SparseCategoricalCrossentropy @@ -90,29 +101,23 @@ model.fit( ) ``` +
+ -Note a very common pitfall here — you *can* just pass the name of the loss as a string to Keras, but by default Keras will assume that you have already applied a softmax to your outputs. Many models, however, output the values right before the softmax is applied, which are also known as the *logits*. We need to tell the loss function that that's what our model does, and the only way to do that is to call it directly, rather than by name with a string. +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax به آنها اعمال شود به خروجی می‌دهند، که همچنین به عنوان *logits* شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. - -### Improving training performance +### بهبود کارایی تعلیم -If you try the above code, it certainly runs, but you'll find that the loss declines only slowly or sporadically. The primary cause -is the *learning rate*. As with the loss, when we pass Keras the name of an optimizer as a string, Keras initializes -that optimizer with default values for all parameters, including learning rate. From long experience, though, we know -that transformer models benefit from a much lower learning rate than the default for Adam, which is 1e-3, also written -as 10 to the power of -3, or 0.001. 5e-5 (0.00005), which is some twenty times lower, is a much better starting point. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. -In addition to lowering the learning rate, we have a second trick up our sleeve: We can slowly reduce the learning rate -over the course of training. In the literature, you will sometimes see this referred to as *decaying* or *annealing* -the learning rate. In Keras, the best way to do this is to use a *learning rate scheduler*. A good one to use is -`PolynomialDecay` — despite the name, with default settings it simply linearly decays the learning rate from the initial -value to the final value over the course of training, which is exactly what we want. In order to use a scheduler correctly, -though, we need to tell it how long training is going to be. We compute that as `num_train_steps` below. +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. + +
```py from tensorflow.keras.optimizers.schedules import PolynomialDecay @@ -131,13 +136,17 @@ from tensorflow.keras.optimizers import Adam opt = Adam(learning_rate=lr_scheduler) ``` +
+ -The 🤗 Transformers library also has a `create_optimizer()` function that will create an `AdamW` optimizer with learning rate decay. This is a convenient shortcut that you'll see in detail in future sections of the course. +کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW`، دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. -Now we have our all-new optimizer, and we can try training with it. First, let's reload the model, to reset the changes to the weights from the training run we just did, and then we can compile it with the new optimizer: +اکنون بهینه‌ساز کاملا جدیدمان را در اختیار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: + +
```py import tensorflow as tf @@ -147,41 +156,62 @@ loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) ``` -Now, we fit again: +
+ +حالا دوباره مدل را فیت می‌کنیم: + +
```py model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) ``` +
+ + -💡 If you want to automatically upload your model to the Hub during training, you can pass along a `PushToHubCallback` in the `model.fit()` method. We will learn more about this in [Chapter 4](/course/chapter4/3) +💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. -### Model predictions +### پیش‌بینی‌های مدل +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. -Training and watching the loss go down is all very nice, but what if we want to actually get outputs from the trained model, either to compute some metrics, or to use the model in production? To do that, we can just use the `predict()` method. This will return the *logits* from the output head of the model, one per class. + +
```py preds = model.predict(tf_validation_dataset)["logits"] ``` -We can convert these logits into the model's class predictions by using `argmax` to find the highest logit, which corresponds to the most likely class: +
+ +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم: + +
```py class_preds = np.argmax(preds, axis=1) print(preds.shape, class_preds.shape) ``` +
+ +
+ ```python out (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: +
+ +اکنون، اجازه دهید از `preds` برای محاسبه برخی معیارها استفاده کنیم! ما می‌توانیم معیارهای مرتبط با دیتاسِت MRPC را، به همان آسانی که دیتاسِت را بارگذاری کردیم، بارگذاری کنیم اما این بار با استفاده از تابع `load_metric()`. شیء باز گردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه معیارها از آن استفاده کنیم: + +
```py from datasets import load_metric @@ -190,10 +220,18 @@ metric = load_metric("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` +
+ +
+ ```python out {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` -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. +
+ +از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. + +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-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. +
\ No newline at end of file From bda0cc13628432f54ef5410e60133c572b70cc02 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 13:09:14 +0200 Subject: [PATCH 338/789] text formatting --- chapters/fa/chapter3/3_tf.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 1bed20661..1b5806d69 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -30,6 +30,7 @@ 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") @@ -53,6 +54,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
+ ### تعلیم مدل‌های تِنسورفِلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. @@ -113,7 +115,7 @@ model.fit( -اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که 1e-3 می‌باشد و به صورت‌ 10 به توان -3 یا 0.001 نیز نوشته می‌شود. +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که ۱e-۳ می‌باشد و به صورت‌ ۱۰ به توان یا ۰،۰۰۱ نیز نوشته می‌شود. علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. From 7e9ef4f019d4f2217ce6ee75af81d54baac03158 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 13:15:18 +0200 Subject: [PATCH 339/789] text formatting --- chapters/fa/chapter3/3.mdx | 2 +- chapters/fa/chapter3/3_tf.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx index 7880d346a..aa64dc157 100644 --- a/chapters/fa/chapter3/3.mdx +++ b/chapters/fa/chapter3/3.mdx @@ -130,7 +130,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (پیش‌بینی‌، در مجموع و به طور میانگین، چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آن را به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. -همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها لاجیت‌های هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر لاجیت‌ها را باز می‌گردانند). برای تبدیل لاجیت‌ها به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها logits مربوط به هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر logits را باز می‌گردانند). برای تبدیل logits به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم:
diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index 1b5806d69..ecb3d4b08 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -173,7 +173,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [Chapter 4](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. +💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. From 985fd5f9261d3e0ba38d68f63c39f3ba07d81345 Mon Sep 17 00:00:00 2001 From: gitkamyah Date: Thu, 23 Jun 2022 13:26:48 +0200 Subject: [PATCH 340/789] text formatting error fixe --- chapters/fa/chapter3/3_tf.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx index ecb3d4b08..fb49d492f 100644 --- a/chapters/fa/chapter3/3_tf.mdx +++ b/chapters/fa/chapter3/3_tf.mdx @@ -117,7 +117,7 @@ model.fit( اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که ۱e-۳ می‌باشد و به صورت‌ ۱۰ به توان یا ۰،۰۰۱ نیز نوشته می‌شود. -علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از *زمان‌بند نرخ یادگیری* است.* یک زمان‌بند خوب برای استفاده زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی* یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از زمان‌بند نرخ یادگیری است. یک زمان‌بند خوب برای استفاده، زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم.
@@ -232,8 +232,9 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l
-از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل 85.78% و F1 score معادل 89.97 روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با 88.9 برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. +از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل ۸۵.۷۸٪ و F1 score معادل ۸۹.۹۷٪ روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با ۸۸.۹ برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. + +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`، با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید، کوک کنید. -به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`، با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید، کوک کنید.
\ No newline at end of file From 147496a15e5908f9ea943431d1c7b07df8ea69f3 Mon Sep 17 00:00:00 2001 From: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Date: Mon, 27 Jun 2022 08:38:40 +0100 Subject: [PATCH 341/789] Translation of 1/7, 1/8, and 1/9. (#263) --- chapters/it/_toctree.yml | 6 ++++++ chapters/it/chapter1/7.mdx | 16 ++++++++++++++++ chapters/it/chapter1/8.mdx | 32 ++++++++++++++++++++++++++++++++ chapters/it/chapter1/9.mdx | 11 +++++++++++ 4 files changed, 65 insertions(+) create mode 100644 chapters/it/chapter1/7.mdx create mode 100644 chapters/it/chapter1/8.mdx create mode 100644 chapters/it/chapter1/9.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index a08938643..e5d8e859b 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 | From 71f71e4ace0b3d06baebe952a4c50800ea2ee515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Mon, 27 Jun 2022 04:45:53 -0300 Subject: [PATCH 342/789] [PT] add chapter 8.1 and 8.2 (#265) --- chapters/pt/_toctree.yml | 7 + chapters/pt/chapter8/1.mdx | 12 ++ chapters/pt/chapter8/2.mdx | 366 +++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 chapters/pt/chapter8/1.mdx create mode 100644 chapters/pt/chapter8/2.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 168333305..918094e0b 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -71,6 +71,13 @@ - 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 + - title: Evento do curso Hugging Face sections: - local: event/1 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 From 712d3b1e538f4431af78e7a5a857e606d8c02588 Mon Sep 17 00:00:00 2001 From: Pavel <60391448+pdumin@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:11:48 +0300 Subject: [PATCH 343/789] [RU] Chapter 4 (#269) --- 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 +++++++++++++ 5 files changed, 955 insertions(+) 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 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." + } + ]} +/> From 228a2186712727b965c990a80ca04fa9fb1dbfdb Mon Sep 17 00:00:00 2001 From: Suteera Seeha <33692408+meanna@users.noreply.github.com> Date: Thu, 30 Jun 2022 14:14:48 +0200 Subject: [PATCH 344/789] Add Thai translation for chapter 6.3b to 6.10 (#268) --- 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 + 11 files changed, 2869 insertions(+), 20 deletions(-) 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/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 89c79d3cb5d8e4e62030c253dc3ad4701e4f0206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Thu, 30 Jun 2022 09:31:08 -0300 Subject: [PATCH 345/789] add 8.3 (#266) --- chapters/pt/_toctree.yml | 2 + chapters/pt/chapter8/3.mdx | 163 +++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 chapters/pt/chapter8/3.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 918094e0b..e7c04b9c7 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -77,6 +77,8 @@ 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: 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! + From a6cb07c2cc9a3ea1cbc4894c63099e847e6470b3 Mon Sep 17 00:00:00 2001 From: Victor Costa <54755870+victorescosta@users.noreply.github.com> Date: Thu, 30 Jun 2022 09:35:07 -0300 Subject: [PATCH 346/789] 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall --- chapters/pt/_toctree.yml | 3 +- chapters/pt/chapter1/3.mdx | 331 +++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 chapters/pt/chapter1/3.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index e7c04b9c7..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: @@ -84,4 +86,3 @@ 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. From f5d6f463bde369b5c13f0aefb09e5d0ac55a3134 Mon Sep 17 00:00:00 2001 From: atgctg <105969161+atgctg@users.noreply.github.com> Date: Tue, 5 Jul 2022 09:00:45 +0200 Subject: [PATCH 347/789] Fix typo (#271) --- chapters/en/chapter6/10.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } From b59a5ec8f0afe57a28b677ff76f1c234f7ebba6e Mon Sep 17 00:00:00 2001 From: Thiago Medeiros Date: Tue, 5 Jul 2022 04:42:02 -0300 Subject: [PATCH 348/789] [PT] add chapter 6.1 (#273) --- chapters/pt/_toctree.yml | 5 +++++ chapters/pt/chapter6/1.mdx | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 chapters/pt/chapter6/1.mdx 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 e46ab85da4c04fbab88aee934cf2dbd4add3a202 Mon Sep 17 00:00:00 2001 From: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Date: Wed, 6 Jul 2022 21:22:07 +0900 Subject: [PATCH 349/789] add Japanese chapter7 (#267) --- 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 ++++++++++ 10 files changed, 6717 insertions(+) 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 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. 質問応答では通常どのように後処理が行われますか? + + From 610d61cf466d855228ed3e2a80a412552dd2858d Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Wed, 13 Jul 2022 12:20:39 +0800 Subject: [PATCH 350/789] zh-CN - Chapter 4,5finished --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 2101 +++++++++++++------------ chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/fr/chapter3/3_tf.mdx | 379 +++-- chapters/fr/chapter5/4.mdx | 592 +++---- chapters/fr/chapter6/8.mdx | 1130 +++++++------- chapters/fr/chapter7/2.mdx | 1955 ++++++++++++------------ chapters/fr/chapter7/4.mdx | 1993 ++++++++++++------------ chapters/fr/chapter7/5.mdx | 2165 +++++++++++++------------- chapters/fr/chapter7/7.mdx | 2457 +++++++++++++++--------------- chapters/hi/chapter1/3.mdx | 4 +- chapters/hi/chapter3/3_tf.mdx | 3 +- chapters/it/chapter1/3.mdx | 4 +- chapters/ja/chapter7/2.mdx | 17 +- chapters/ja/chapter7/4.mdx | 5 +- chapters/ja/chapter7/5.mdx | 3 +- chapters/ja/chapter7/7.mdx | 5 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/pt/chapter5/4.mdx | 2 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/ru/chapter2/2.mdx | 5 +- chapters/ru/chapter3/3_tf.mdx | 3 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/th/chapter3/3_tf.mdx | 3 +- chapters/th/chapter6/8.mdx | 4 +- chapters/zh-CN/_toctree.yml | 44 +- chapters/zh-CN/chapter0/1.mdx | 2 +- chapters/zh-CN/chapter1/1.mdx | 2 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/1.mdx | 2 +- chapters/zh-CN/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter3/1.mdx | 2 +- chapters/zh-CN/chapter3/3_tf.mdx | 3 +- 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 +++ 59 files changed, 9950 insertions(+), 6519 deletions(-) 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/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index dd1be7835..566457a0e 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..cd6aee466 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..313c1fc53 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 2252a9613..6d43884e4 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..b7d2609f7 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..c7cef7308 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,9 +404,7 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 9d1ccc3b9..5a99e497c 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,9 +413,7 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -663,9 +661,7 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -774,10 +770,7 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -788,9 +781,7 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 5aa654ceb..2d599c3c7 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,10 +795,7 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 958dc685d..c9d184f35 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,1051 +1,1050 @@ - - -# 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. + + +# 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/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d8e1942e4..8e18ac50b 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,10 +1029,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..04ac7f60a 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 71abc5e16..1ab6e636d 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,10 +43,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 6be37c3a3..16569ef4d 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -1,190 +1,189 @@ - - -# 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/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index dc286c718..4fe96aa5e 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/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 46440deb7..0d27ff399 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,566 +1,564 @@ -# 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/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index de780128e..6d3dd77ac 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,982 +1,973 @@ - - -# 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 `load_metric()` 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 `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 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 `load_metric()` 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 `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 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/4.mdx b/chapters/fr/chapter7/4.mdx index d7d868936..5ef049a28 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,998 +1,995 @@ - - -# 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, load_metric - -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 `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 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, load_metric + +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 `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 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 47428e425..add05da40 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1083 +1,1082 @@ - - -# 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 -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 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 +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 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/7.mdx b/chapters/fr/chapter7/7.mdx index 359e84211..e027863bd 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1,1230 +1,1227 @@ - - -# 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 🤗 *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'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 🤗 *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'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/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index d40137645..3d25dcdcb 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -166,9 +166,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 84f022ead..666894267 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..3690bcae5 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index c6fad9d03..c4f03d92b 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -419,9 +419,7 @@ label2id = {v: k for k, v in id2label.items()} from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -685,9 +683,7 @@ label2id = {v: k for k, v in id2label.items()} from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -806,10 +802,7 @@ trainer.push_to_hub(commit_message="Training complete") from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -820,9 +813,7 @@ eval_dataloader = DataLoader( ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 969647748..7dcf8b53b 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -817,10 +817,7 @@ 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, + 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 diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 3f83c6dad..3e150c1c8 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -940,8 +940,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index e54482205..2af535bd0 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -1039,10 +1039,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..3359ab062 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 254d83372..2ed9713ae 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -152,9 +152,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..b689c074d 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index fc1f3f92e..ebf1df750 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -88,7 +88,7 @@ Aqui o atributo `rss` refere-se ao _tamanho do conjunto residente_, que é a fra ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..008db7af8 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 85ce9cbd5..630074deb 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 01f73e339..8822d0ea9 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..a72f16354 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,9 +151,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..24718bd2d 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index 6e6941754..76081e711 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -86,8 +86,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 8b8d62072..16a4efd3d 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -429,9 +429,7 @@ tokenizer.decode(encoding.ids) from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` 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/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..1e7e91108 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,9 +132,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` ```python out 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/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 2bf0ef5f8..bea755456 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` 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/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index 911e12a92..a1b3daa87 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` 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..7fef6181c --- /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 d804922c6ca1f78ea8ee9a2f93c9646583353457 Mon Sep 17 00:00:00 2001 From: Leandro von Werra Date: Thu, 21 Jul 2022 13:32:54 +0200 Subject: [PATCH 351/789] replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun --- 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 875797744..bd41c5dcb 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -185,11 +185,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 c34bfadb830384d649f925921bdb01b50ff74326 Mon Sep 17 00:00:00 2001 From: Bhadresh Savani Date: Mon, 25 Jul 2022 13:25:38 +0530 Subject: [PATCH 352/789] [GJ] Translation to Gujarati - Ch0 Setup (#287) --- chapters/gj/_toctree.yml | 5 +- chapters/gj/chapter0/1.mdx | 99 +++++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 4 deletions(-) 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 From eae6501dac377d5de2b8aa497894b7d10a750401 Mon Sep 17 00:00:00 2001 From: Thiago Medeiros Date: Mon, 25 Jul 2022 06:35:58 -0300 Subject: [PATCH 353/789] [PT] add chapter 6.2 and 6.3 (#279) --- chapters/pt/_toctree.yml | 5 + chapters/pt/chapter6/2.mdx | 252 ++++++++++++++++++++ chapters/pt/chapter6/3.mdx | 471 +++++++++++++++++++++++++++++++++++++ 3 files changed, 728 insertions(+) create mode 100644 chapters/pt/chapter6/2.mdx create mode 100644 chapters/pt/chapter6/3.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 9d7266369..877d73770 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -72,6 +72,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/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. From 1537727c22ada750c3dcb4252a4ecfdc6034daed Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 25 Jul 2022 10:44:47 +0100 Subject: [PATCH 354/789] Fix formatting --- chapters/de/chapter3/3_tf.mdx | 3 ++- chapters/en/chapter1/3.mdx | 4 +++- chapters/en/chapter2/2.mdx | 5 ++++- chapters/en/chapter3/3_tf.mdx | 3 ++- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +++- chapters/en/chapter7/2.mdx | 17 +++++++++++++---- chapters/en/chapter7/4.mdx | 5 ++++- chapters/en/chapter7/5.mdx | 3 ++- chapters/en/chapter7/7.mdx | 5 ++++- chapters/es/chapter1/3.mdx | 4 +++- chapters/fa/chapter2/2.mdx | 5 ++++- chapters/fr/chapter3/3_tf.mdx | 3 ++- chapters/fr/chapter5/4.mdx | 2 +- chapters/fr/chapter6/8.mdx | 4 +++- chapters/fr/chapter7/2.mdx | 17 +++++++++++++---- chapters/fr/chapter7/4.mdx | 5 ++++- chapters/fr/chapter7/5.mdx | 3 ++- chapters/fr/chapter7/7.mdx | 5 ++++- chapters/hi/chapter1/3.mdx | 4 +++- chapters/hi/chapter3/3_tf.mdx | 3 ++- chapters/it/chapter1/3.mdx | 4 +++- chapters/ja/chapter7/2.mdx | 17 +++++++++++++---- chapters/ja/chapter7/4.mdx | 5 ++++- chapters/ja/chapter7/5.mdx | 3 ++- chapters/ja/chapter7/7.mdx | 5 ++++- chapters/ko/chapter1/3.mdx | 4 +++- chapters/pt/chapter1/3.mdx | 4 +++- chapters/pt/chapter2/2.mdx | 5 ++++- chapters/pt/chapter5/4.mdx | 2 +- chapters/ru/chapter1/3.mdx | 4 +++- chapters/ru/chapter2/2.mdx | 5 ++++- chapters/ru/chapter3/3_tf.mdx | 3 ++- chapters/th/chapter1/3.mdx | 4 +++- chapters/th/chapter2/2.mdx | 5 ++++- chapters/th/chapter3/3_tf.mdx | 3 ++- chapters/th/chapter6/8.mdx | 4 +++- chapters/zh-CN/chapter1/3.mdx | 4 +++- chapters/zh-CN/chapter2/2.mdx | 5 ++++- chapters/zh-CN/chapter3/3_tf.mdx | 3 ++- chapters/zh-CN/chapter5/4.mdx | 2 +- 41 files changed, 147 insertions(+), 50 deletions(-) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 566457a0e..dd1be7835 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index cd6aee466..ac22e7e8f 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index 313c1fc53..d1304d737 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 6d43884e4..2252a9613 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index b7d2609f7..cb90067f4 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index c7cef7308..301648c7e 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,7 +404,9 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 5a99e497c..9d1ccc3b9 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,7 +413,9 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -661,7 +663,9 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -770,7 +774,10 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -781,7 +788,9 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 2d599c3c7..5aa654ceb 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,7 +795,10 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index c9d184f35..1796739cf 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -928,7 +928,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index 8e18ac50b..d8e1942e4 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,7 +1029,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index 04ac7f60a..c725bb68d 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 1ab6e636d..71abc5e16 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,7 +43,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 16569ef4d..f049e988e 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 4fe96aa5e..4c73d15c5 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -90,7 +90,7 @@ Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, q ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 0d27ff399..d99ca14d9 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -405,7 +405,9 @@ Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenize from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 6d3dd77ac..53ed23dc8 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -413,7 +413,9 @@ Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTok from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -661,7 +663,9 @@ Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenC from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -770,7 +774,10 @@ D'abord nous devons construire le `DataLoader`s à partir de nos jeux de donnée from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -781,7 +788,9 @@ Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne conti ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 5ef049a28..f7845ef69 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -792,7 +792,10 @@ 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, + 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 diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index add05da40..66f092e1a 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -954,7 +954,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e027863bd..7b71a6929 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1050,7 +1050,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index 3d25dcdcb..d40137645 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -166,7 +166,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 666894267..84f022ead 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 3690bcae5..7fb506a94 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index c4f03d92b..c6fad9d03 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -419,7 +419,9 @@ label2id = {v: k for k, v in id2label.items()} from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -683,7 +685,9 @@ label2id = {v: k for k, v in id2label.items()} from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -802,7 +806,10 @@ trainer.push_to_hub(commit_message="Training complete") from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -813,7 +820,9 @@ eval_dataloader = DataLoader( ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 7dcf8b53b..969647748 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -817,7 +817,10 @@ 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, + 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 diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 3e150c1c8..3f83c6dad 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -940,7 +940,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index 2af535bd0..e54482205 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -1039,7 +1039,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index 3359ab062..f32892430 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 2ed9713ae..254d83372 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -152,7 +152,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index b689c074d..88c9a068e 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index ebf1df750..fc1f3f92e 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -88,7 +88,7 @@ Aqui o atributo `rss` refere-se ao _tamanho do conjunto residente_, que é a fra ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 008db7af8..2f28dc98a 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 630074deb..85ce9cbd5 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 8822d0ea9..01f73e339 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index a72f16354..9ab990db5 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,7 +151,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 24718bd2d..87968254b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index 76081e711..6e6941754 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -86,7 +86,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 16a4efd3d..8b8d62072 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -429,7 +429,9 @@ tokenizer.decode(encoding.ids) from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 1e7e91108..076263ba4 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,7 +132,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index bea755456..2bf0ef5f8 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index a1b3daa87..911e12a92 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index 7fef6181c..d8224b3bd 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -88,7 +88,7 @@ RAM used: 5678.33 MB ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` From bb26db552a70c3359d46f7acb07e05a8006fe3f6 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 25 Jul 2022 10:51:13 +0100 Subject: [PATCH 355/789] Debug formatting --- chapters/en/chapter7/5.mdx | 2102 ++++++++++++++++++------------------ 1 file changed, 1051 insertions(+), 1051 deletions(-) diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 59c05715f..e6df6fc31 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. From 69187a3789e8d3d2d0de821ebe495f111d1cc73d Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Mon, 25 Jul 2022 10:52:16 +0100 Subject: [PATCH 356/789] Debug FR formatting --- chapters/fr/chapter3/3_tf.mdx | 380 ++--- chapters/fr/chapter5/4.mdx | 592 ++++---- chapters/fr/chapter6/8.mdx | 1132 +++++++-------- chapters/fr/chapter7/2.mdx | 1964 +++++++++++++------------- chapters/fr/chapter7/4.mdx | 1996 +++++++++++++------------- chapters/fr/chapter7/5.mdx | 2166 ++++++++++++++--------------- chapters/fr/chapter7/7.mdx | 2460 ++++++++++++++++----------------- 7 files changed, 5345 insertions(+), 5345 deletions(-) diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index eb0a24038..9a84d533d 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/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 4c73d15c5..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 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/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index d99ca14d9..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. 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/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index b37f76539..921f9629f 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/4.mdx b/chapters/fr/chapter7/4.mdx index fe946b17e..e28cf05d5 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 099289eb1..c2177fb07 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/7.mdx b/chapters/fr/chapter7/7.mdx index bb5866b26..b703523bd 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 ! From 69accea847dc53ef30f6ad6d10019b649a9e36e8 Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Mon, 25 Jul 2022 18:06:52 +0800 Subject: [PATCH 357/789] zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall --- 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 ++++++++++ 19 files changed, 3536 insertions(+), 8 deletions(-) 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/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 be15274d98ddf688ff67378b5f2f5e7a5513a1cf Mon Sep 17 00:00:00 2001 From: Victor Costa <54755870+victorescosta@users.noreply.github.com> Date: Mon, 25 Jul 2022 17:20:03 -0300 Subject: [PATCH 358/789] Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall --- chapters/pt/_toctree.yml | 16 +++ 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 ++ 8 files changed, 519 insertions(+) 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 diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 877d73770..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: 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 | From 4656735ff9389f2946a3bb1c6aa3f6d2b030b8ea Mon Sep 17 00:00:00 2001 From: Andreas Ehrencrona Date: Sun, 31 Jul 2022 14:11:28 +0200 Subject: [PATCH 359/789] tf_default_data_collator seems to have moved --- chapters/en/chapter7/3.mdx | 2 +- chapters/fr/chapter7/3.mdx | 2 +- chapters/ja/chapter7/3.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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 From e69fce28d3a7b35b76c4f768a6cedf295b37d8c9 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Mon, 1 Aug 2022 10:41:59 +0800 Subject: [PATCH 360/789] zh-CN - Chapter 6finished --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 3 +- chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/hi/chapter1/3.mdx | 4 +- chapters/hi/chapter3/3_tf.mdx | 3 +- chapters/it/chapter1/3.mdx | 4 +- chapters/ja/chapter7/2.mdx | 17 +- chapters/ja/chapter7/4.mdx | 5 +- chapters/ja/chapter7/5.mdx | 3 +- chapters/ja/chapter7/7.mdx | 5 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/pt/chapter5/4.mdx | 2 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/ru/chapter2/2.mdx | 5 +- chapters/ru/chapter3/3_tf.mdx | 3 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/th/chapter3/3_tf.mdx | 3 +- chapters/th/chapter6/8.mdx | 4 +- chapters/zh-CN/_toctree.yml | 27 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter3/3_tf.mdx | 3 +- chapters/zh-CN/chapter5/4.mdx | 2 +- 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 | 562 +++++++++++++++++++++++++++ chapters/zh-CN/chapter6/9.mdx | 11 + 46 files changed, 3527 insertions(+), 119 deletions(-) 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/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 6290506eb..8feefce99 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..cd6aee466 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..313c1fc53 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 6357be0b2..275ee0483 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..b7d2609f7 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..c7cef7308 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,9 +404,7 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 3eaba62c8..30caddc48 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,9 +413,7 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -663,9 +661,7 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -774,10 +770,7 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -788,9 +781,7 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index e68bb376b..d2a95235c 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,10 +795,7 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index e6df6fc31..480f29572 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -928,8 +928,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d32fc7d8d..9c55b31ce 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,10 +1029,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..04ac7f60a 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 71abc5e16..1ab6e636d 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,10 +43,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index d40137645..3d25dcdcb 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -166,9 +166,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 837983be3..053656a3b 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..3690bcae5 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index efd90ad71..f46a61030 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -419,9 +419,7 @@ label2id = {v: k for k, v in id2label.items()} from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -685,9 +683,7 @@ label2id = {v: k for k, v in id2label.items()} from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` @@ -806,10 +802,7 @@ trainer.push_to_hub(commit_message="Training complete") from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, + 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 @@ -820,9 +813,7 @@ eval_dataloader = DataLoader( ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, + model_checkpoint, id2label=id2label, label2id=label2id, ) ``` diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index cadd8c24c..3a930ac2a 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -817,10 +817,7 @@ 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, + 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 diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 13232100e..b52303af4 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -940,8 +940,7 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index 8ee20d14f..35affcd1e 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -1039,10 +1039,7 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, + 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 diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..3359ab062 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,9 +150,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 254d83372..2ed9713ae 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -152,9 +152,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..b689c074d 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index fc1f3f92e..ebf1df750 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -88,7 +88,7 @@ Aqui o atributo `rss` refere-se ao _tamanho do conjunto residente_, que é a fra ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..008db7af8 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,9 +153,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 85ce9cbd5..630074deb 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index a3b1f7ef6..2d0435eb8 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..a72f16354 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,9 +151,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..24718bd2d 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index e9ccd4611..42960416b 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -86,8 +86,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 8b8d62072..16a4efd3d 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -429,9 +429,7 @@ tokenizer.decode(encoding.ids) from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", + tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", ) ``` 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/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..1e7e91108 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,9 +132,7 @@ 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, + "In this course, we will teach you how to", max_length=30, num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 2bf0ef5f8..bea755456 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,10 +39,7 @@ 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!", - ] + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!",] ) ``` diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index be3953a8c..d7647e35c 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -85,8 +85,7 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, + tf_train_dataset, validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index d8224b3bd..7fef6181c 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -88,7 +88,7 @@ RAM used: 5678.33 MB ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) +size_gb = pubmed_dataset.dataset_size / (1024 ** 3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` 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..fc67c8855 --- /dev/null +++ b/chapters/zh-CN/chapter6/8.mdx @@ -0,0 +1,562 @@ +# 逐块地构建标记器 + + + +正如我们在前几节中看到的,标记化包括几个步骤: + +- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、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 6403eaa453188e79b08473868ff40dd4060669f7 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Mon, 1 Aug 2022 10:44:46 +0800 Subject: [PATCH 361/789] Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. --- chapters/pt/_toctree.yml | 16 --- 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 -- 8 files changed, 519 deletions(-) delete mode 100644 chapters/pt/chapter1/10.mdx delete mode 100644 chapters/pt/chapter1/4.mdx delete mode 100644 chapters/pt/chapter1/5.mdx delete mode 100644 chapters/pt/chapter1/6.mdx delete mode 100644 chapters/pt/chapter1/7.mdx delete mode 100644 chapters/pt/chapter1/8.mdx delete mode 100644 chapters/pt/chapter1/9.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 425eb7d94..877d73770 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -11,22 +11,6 @@ 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: diff --git a/chapters/pt/chapter1/10.mdx b/chapters/pt/chapter1/10.mdx deleted file mode 100644 index e1a79c2ce..000000000 --- a/chapters/pt/chapter1/10.mdx +++ /dev/null @@ -1,252 +0,0 @@ - - -# 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 deleted file mode 100644 index f9b9e1ce8..000000000 --- a/chapters/pt/chapter1/4.mdx +++ /dev/null @@ -1,171 +0,0 @@ -# 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 deleted file mode 100644 index 4ab25d749..000000000 --- a/chapters/pt/chapter1/5.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index 0d6e1cb15..000000000 --- a/chapters/pt/chapter1/6.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# 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 deleted file mode 100644 index 695359a16..000000000 --- a/chapters/pt/chapter1/7.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# 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 deleted file mode 100644 index e01fd445a..000000000 --- a/chapters/pt/chapter1/8.mdx +++ /dev/null @@ -1,24 +0,0 @@ -# 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 deleted file mode 100644 index 7398a7fc6..000000000 --- a/chapters/pt/chapter1/9.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# 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 | From 43fc1f7135796c4d3bcdcadd18255c83489d1c85 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Mon, 1 Aug 2022 10:47:28 +0800 Subject: [PATCH 362/789] Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. --- chapters/de/chapter3/3_tf.mdx | 3 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter2/2.mdx | 5 +- chapters/en/chapter3/3_tf.mdx | 3 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 17 +- chapters/en/chapter7/4.mdx | 5 +- chapters/en/chapter7/5.mdx | 3 +- chapters/en/chapter7/7.mdx | 5 +- chapters/es/chapter1/3.mdx | 4 +- chapters/fa/chapter2/2.mdx | 5 +- chapters/hi/chapter1/3.mdx | 4 +- chapters/hi/chapter3/3_tf.mdx | 3 +- chapters/it/chapter1/3.mdx | 4 +- chapters/ja/chapter7/2.mdx | 17 +- chapters/ja/chapter7/4.mdx | 5 +- chapters/ja/chapter7/5.mdx | 3 +- chapters/ja/chapter7/7.mdx | 5 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/pt/chapter1/3.mdx | 4 +- chapters/pt/chapter2/2.mdx | 5 +- chapters/pt/chapter5/4.mdx | 2 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/ru/chapter2/2.mdx | 5 +- chapters/ru/chapter3/3_tf.mdx | 3 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter2/2.mdx | 5 +- chapters/th/chapter3/3_tf.mdx | 3 +- chapters/th/chapter6/8.mdx | 4 +- chapters/zh-CN/_toctree.yml | 27 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 5 +- chapters/zh-CN/chapter3/3_tf.mdx | 3 +- chapters/zh-CN/chapter5/4.mdx | 2 +- 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 | 562 --------------------------- chapters/zh-CN/chapter6/9.mdx | 11 - 46 files changed, 119 insertions(+), 3527 deletions(-) delete mode 100644 chapters/zh-CN/chapter6/1.mdx delete mode 100644 chapters/zh-CN/chapter6/10.mdx delete mode 100644 chapters/zh-CN/chapter6/2.mdx delete mode 100644 chapters/zh-CN/chapter6/3.mdx delete mode 100644 chapters/zh-CN/chapter6/3b.mdx delete mode 100644 chapters/zh-CN/chapter6/4.mdx delete mode 100644 chapters/zh-CN/chapter6/5.mdx delete mode 100644 chapters/zh-CN/chapter6/6.mdx delete mode 100644 chapters/zh-CN/chapter6/7.mdx delete mode 100644 chapters/zh-CN/chapter6/8.mdx delete mode 100644 chapters/zh-CN/chapter6/9.mdx diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 8feefce99..6290506eb 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index cd6aee466..ac22e7e8f 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index 313c1fc53..d1304d737 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 275ee0483..6357be0b2 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index b7d2609f7..cb90067f4 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -88,7 +88,7 @@ Here the `rss` attribute refers to the _resident set size_, which is the fractio ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index c7cef7308..301648c7e 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -404,7 +404,9 @@ Great! Now that we're done, we can save the tokenizer like before, and wrap it i from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 30caddc48..3eaba62c8 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -413,7 +413,9 @@ Now we can just pass them to the `TFAutoModelForTokenClassification.from_pretrai from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -661,7 +663,9 @@ Now we can just pass them to the `AutoModelForTokenClassification.from_pretraine from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -770,7 +774,10 @@ First we need to build the `DataLoader`s from our datasets. We'll reuse our `dat from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -781,7 +788,9 @@ Next we reinstantiate our model, to make sure we're not continuing the fine-tuni ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index d2a95235c..e68bb376b 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -795,7 +795,10 @@ 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, + 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 diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 480f29572..e6df6fc31 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -928,7 +928,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index 9c55b31ce..d32fc7d8d 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1029,7 +1029,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index 04ac7f60a..c725bb68d 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 1ab6e636d..71abc5e16 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -43,7 +43,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index 3d25dcdcb..d40137645 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -166,7 +166,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 053656a3b..837983be3 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 3690bcae5..7fb506a94 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index f46a61030..efd90ad71 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -419,7 +419,9 @@ label2id = {v: k for k, v in id2label.items()} from transformers import TFAutoModelForTokenClassification model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -683,7 +685,9 @@ label2id = {v: k for k, v in id2label.items()} from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` @@ -802,7 +806,10 @@ trainer.push_to_hub(commit_message="Training complete") from torch.utils.data import DataLoader train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, + 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 @@ -813,7 +820,9 @@ eval_dataloader = DataLoader( ```py model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, id2label=id2label, label2id=label2id, + model_checkpoint, + id2label=id2label, + label2id=label2id, ) ``` diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 3a930ac2a..cadd8c24c 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -817,7 +817,10 @@ 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, + 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 diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index b52303af4..13232100e 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -940,7 +940,8 @@ for epoch in range(num_train_epochs): 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"], + batch["input_ids"], + attention_mask=batch["attention_mask"], ) generated_tokens = accelerator.pad_across_processes( diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index 35affcd1e..8ee20d14f 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -1039,7 +1039,10 @@ validation_set = validation_dataset.remove_columns(["example_id", "offset_mappin validation_set.set_format("torch") train_dataloader = DataLoader( - train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=8, + 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 diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index 3359ab062..f32892430 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -150,7 +150,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 2ed9713ae..254d83372 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -152,7 +152,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index b689c074d..88c9a068e 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index ebf1df750..fc1f3f92e 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -88,7 +88,7 @@ Aqui o atributo `rss` refere-se ao _tamanho do conjunto residente_, que é a fra ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 008db7af8..2f28dc98a 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -153,7 +153,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 630074deb..85ce9cbd5 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 2d0435eb8..a3b1f7ef6 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index a72f16354..9ab990db5 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -151,7 +151,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 24718bd2d..87968254b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index 42960416b..e9ccd4611 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -86,7 +86,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 16a4efd3d..8b8d62072 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -429,7 +429,9 @@ tokenizer.decode(encoding.ids) from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index b23fbbc78..499368392 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -80,7 +80,7 @@ title: 章末小测验 quiz: 4 -- title: 5. 🤗 Datasets库 +- title: 5. The 🤗 Datasets library sections: - local: chapter5/1 title: 本章简介 @@ -99,28 +99,3 @@ - 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/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 1e7e91108..076263ba4 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -132,7 +132,9 @@ 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, + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, ) ``` ```python out diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index bea755456..2bf0ef5f8 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -39,7 +39,10 @@ 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!",] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] ) ``` diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index d7647e35c..be3953a8c 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -85,7 +85,8 @@ model.compile( metrics=["accuracy"], ) model.fit( - tf_train_dataset, validation_data=tf_validation_dataset, + tf_train_dataset, + validation_data=tf_validation_dataset, ) ``` diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index 7fef6181c..d8224b3bd 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -88,7 +88,7 @@ RAM used: 5678.33 MB ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024 ** 3) +size_gb = pubmed_dataset.dataset_size / (1024**3) print(f"Dataset size (cache file) : {size_gb:.2f} GB") ``` diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx deleted file mode 100644 index a13faa5a1..000000000 --- a/chapters/zh-CN/chapter6/1.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# 本章简介 - -在 [第三章] (/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 deleted file mode 100644 index 703459a3d..000000000 --- a/chapters/zh-CN/chapter6/10.mdx +++ /dev/null @@ -1,268 +0,0 @@ - - -# 章末小测验 - -让我们测试一下您在本章中学到了什么! - -### 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 deleted file mode 100644 index ffac12aa8..000000000 --- a/chapters/zh-CN/chapter6/2.mdx +++ /dev/null @@ -1,256 +0,0 @@ -# 根据已有的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 deleted file mode 100644 index f1ed19153..000000000 --- a/chapters/zh-CN/chapter6/3.mdx +++ /dev/null @@ -1,473 +0,0 @@ - - -# 快速标记器的特殊能力 - -{#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 deleted file mode 100644 index c6012e419..000000000 --- a/chapters/zh-CN/chapter6/3b.mdx +++ /dev/null @@ -1,639 +0,0 @@ - - -# 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 deleted file mode 100644 index 5e58d2747..000000000 --- a/chapters/zh-CN/chapter6/4.mdx +++ /dev/null @@ -1,124 +0,0 @@ -# 标准化和预标记化 - - - -在我们更深入地研究与 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 deleted file mode 100644 index 272210fad..000000000 --- a/chapters/zh-CN/chapter6/5.mdx +++ /dev/null @@ -1,360 +0,0 @@ -# 字节对编码标记化 - - - -字节对编码(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 deleted file mode 100644 index c93b41898..000000000 --- a/chapters/zh-CN/chapter6/6.mdx +++ /dev/null @@ -1,373 +0,0 @@ -# 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 deleted file mode 100644 index 4303d8e58..000000000 --- a/chapters/zh-CN/chapter6/7.mdx +++ /dev/null @@ -1,381 +0,0 @@ -# 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 deleted file mode 100644 index fc67c8855..000000000 --- a/chapters/zh-CN/chapter6/8.mdx +++ /dev/null @@ -1,562 +0,0 @@ -# 逐块地构建标记器 - - - -正如我们在前几节中看到的,标记化包括几个步骤: - -- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、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 deleted file mode 100644 index b2f5ea052..000000000 --- a/chapters/zh-CN/chapter6/9.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# 标记器,回顾! - -完成这一章,辛苦了! - -在深入研究标记器之后,您应该: - -- 能够使用旧的标记器作为模板来训练新的标记器 -- 了解如何使用偏移量将标记的位置映射到其原始文本范围 -- 了解 BPE、WordPiece 和 Unigram 之间的区别 -- 能够混合和匹配 🤗 Tokenizers 库提供的块来构建您自己的标记器 -- 能够在 🤗 Transformers 库中使用该标记器 \ No newline at end of file From 2bf870d10af29a471415c98e753b335008b0e55c Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Mon, 1 Aug 2022 10:48:38 +0800 Subject: [PATCH 363/789] zh-CN - Chapter 6finished --- 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 | 562 +++++++++++++++++++++++++++++ chapters/zh-CN/chapter6/9.mdx | 11 + 12 files changed, 3487 insertions(+), 1 deletion(-) 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/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..fc67c8855 --- /dev/null +++ b/chapters/zh-CN/chapter6/8.mdx @@ -0,0 +1,562 @@ +# 逐块地构建标记器 + + + +正如我们在前几节中看到的,标记化包括几个步骤: + +- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、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 ef3b87b1239dc471f5e82ba612d1ed0f1f569056 Mon Sep 17 00:00:00 2001 From: leandro Date: Thu, 4 Aug 2022 10:58:24 +0200 Subject: [PATCH 364/789] fix style --- chapters/zh-CN/chapter6/8.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index fc67c8855..c7922a72f 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -403,7 +403,9 @@ tokenizer.decode(encoding.ids) from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, bos_token="<|endoftext|>", eos_token="<|endoftext|>", + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", ) ``` From 85f75d74bfeac3302e8f13a14289a4f8fff1bf57 Mon Sep 17 00:00:00 2001 From: leandro Date: Thu, 4 Aug 2022 11:24:58 +0200 Subject: [PATCH 365/789] undo bad commit --- chapters/pt/_toctree.yml | 16 +++ 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 ++ 8 files changed, 519 insertions(+) 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 diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 877d73770..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: 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 | From 0ef247f21a2f71711c68a12f841e21b49bb296ee Mon Sep 17 00:00:00 2001 From: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Date: Mon, 15 Aug 2022 10:31:25 +0200 Subject: [PATCH 366/789] Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra --- chapters/it/_toctree.yml | 20 + 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 +++++++++++ 9 files changed, 2468 insertions(+) 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 diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index e5d8e859b..40c971827 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -39,3 +39,23 @@ - local: chapter4/6 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 + }, + ]} +/> From 8330d446925dd9c9343ef1e9fe915e722eabdf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93ng=20H=E1=BA=A1nh?= Date: Mon, 22 Aug 2022 09:19:09 +0200 Subject: [PATCH 367/789] Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 4 +- 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 +++++ 36 files changed, 5626 insertions(+), 3 deletions(-) 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/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 9828fd87c66db162f5e874bc5e2b931eacf3f657 Mon Sep 17 00:00:00 2001 From: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Date: Mon, 22 Aug 2022 09:21:05 +0200 Subject: [PATCH 368/789] Translate JP chapter 8 (#249) --- chapters/ja/_toctree.yml | 9 + chapters/ja/chapter8/1.mdx | 12 ++ chapters/ja/chapter8/2.mdx | 359 +++++++++++++++++++++++++++++++++++++ chapters/ja/chapter8/6.mdx | 7 + 4 files changed, 387 insertions(+) create mode 100644 chapters/ja/chapter8/1.mdx create mode 100644 chapters/ja/chapter8/2.mdx create mode 100644 chapters/ja/chapter8/6.mdx 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 From 3d4e59141938929c218f2dc24f65cbee7e0e2b0b Mon Sep 17 00:00:00 2001 From: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Date: Mon, 22 Aug 2022 09:28:25 +0200 Subject: [PATCH 369/789] Italian translation - Chapter 8 (#272) --- 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 +++++++++ 9 files changed, 2128 insertions(+) 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 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? + + From 57115d9064a7a22d0ff7507b20419a88093cb037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93ng=20H=E1=BA=A1nh?= Date: Tue, 23 Aug 2022 15:07:24 +0200 Subject: [PATCH 370/789] Translation to Vietnamese - chapter 5 (#297) --- 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 7a7d48d99beb20b9176b7df6b183b6cb057532ce Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 23 Aug 2022 16:24:49 +0100 Subject: [PATCH 371/789] Add course contributors (#298) --- README.md | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9f46e8eef..1d209b9c5 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) | +| [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 From 3c6b8bb492a859b633bf04ca847740b1f7e6fdd2 Mon Sep 17 00:00:00 2001 From: Mishig Davaadorj Date: Tue, 23 Aug 2022 23:39:14 +0200 Subject: [PATCH 372/789] Add CourseFloatingBanner component --- chapters/bn/chapter1/1.mdx | 5 + chapters/bn/chapter2/1.mdx | 5 + chapters/de/chapter3/1.mdx | 5 + 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/4.mdx | 5 + chapters/en/chapter1/5.mdx | 5 + chapters/en/chapter1/6.mdx | 5 + chapters/en/chapter1/7.mdx | 5 + chapters/en/chapter1/9.mdx | 5 + chapters/en/chapter2/1.mdx | 5 + chapters/en/chapter2/7.mdx | 5 + chapters/en/chapter2/8.mdx | 5 + chapters/en/chapter3/1.mdx | 5 + chapters/en/chapter3/5.mdx | 5 + chapters/en/chapter3/6.mdx | 5 + chapters/en/chapter4/1.mdx | 5 + 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/7.mdx | 5 + chapters/en/chapter5/8.mdx | 5 + chapters/en/chapter6/1.mdx | 5 + chapters/en/chapter6/10.mdx | 5 + chapters/en/chapter6/9.mdx | 5 + chapters/en/chapter7/1.mdx | 5 + chapters/en/chapter7/8.mdx | 5 + chapters/en/chapter7/9.mdx | 5 + chapters/en/chapter8/1.mdx | 5 + chapters/en/chapter8/6.mdx | 5 + chapters/en/chapter8/7.mdx | 5 + chapters/en/chapter9/1.mdx | 5 + chapters/en/chapter9/8.mdx | 5 + chapters/en/chapter9/9.mdx | 471 ++++++++++++------------ chapters/es/chapter1/1.mdx | 5 + chapters/es/chapter1/10.mdx | 5 + chapters/es/chapter1/2.mdx | 5 + chapters/es/chapter1/4.mdx | 5 + chapters/es/chapter1/5.mdx | 5 + chapters/es/chapter1/6.mdx | 5 + chapters/es/chapter1/9.mdx | 5 + chapters/es/chapter3/1.mdx | 5 + chapters/es/chapter8/1.mdx | 5 + chapters/fa/chapter1/1.mdx | 5 + chapters/fa/chapter1/2.mdx | 5 + chapters/fa/chapter2/1.mdx | 5 + chapters/fa/chapter3/1.mdx | 5 + chapters/fa/chapter4/1.mdx | 5 + chapters/fr/chapter1/1.mdx | 115 +++--- chapters/fr/chapter1/10.mdx | 521 +++++++++++++------------- chapters/fr/chapter1/2.mdx | 47 +-- 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/9.mdx | 25 +- chapters/fr/chapter2/1.mdx | 53 +-- chapters/fr/chapter2/7.mdx | 29 +- chapters/fr/chapter2/8.mdx | 619 +++++++++++++++---------------- chapters/fr/chapter3/1.mdx | 49 +-- chapters/fr/chapter3/5.mdx | 43 ++- chapters/fr/chapter3/6.mdx | 597 +++++++++++++++--------------- chapters/fr/chapter4/1.mdx | 37 +- 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/7.mdx | 25 +- chapters/fr/chapter5/8.mdx | 457 +++++++++++------------ chapters/fr/chapter6/1.mdx | 29 +- chapters/fr/chapter6/10.mdx | 561 ++++++++++++++-------------- chapters/fr/chapter6/9.mdx | 27 +- chapters/fr/chapter7/1.mdx | 71 ++-- chapters/fr/chapter7/8.mdx | 37 +- chapters/fr/chapter7/9.mdx | 653 +++++++++++++++++---------------- chapters/fr/chapter8/1.mdx | 29 +- chapters/fr/chapter8/6.mdx | 19 +- chapters/fr/chapter8/7.mdx | 403 ++++++++++---------- chapters/fr/chapter9/1.mdx | 69 ++-- chapters/fr/chapter9/8.mdx | 39 +- 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/4.mdx | 5 + chapters/hi/chapter1/5.mdx | 5 + chapters/hi/chapter1/6.mdx | 5 + chapters/hi/chapter1/7.mdx | 5 + chapters/hi/chapter1/9.mdx | 5 + chapters/hi/chapter2/1.mdx | 5 + chapters/hi/chapter3/1.mdx | 5 + chapters/hi/chapter3/5.mdx | 5 + chapters/hi/chapter3/6.mdx | 5 + chapters/it/chapter1/1.mdx | 5 + chapters/it/chapter1/2.mdx | 5 + 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/9.mdx | 5 + chapters/it/chapter4/1.mdx | 5 + 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/7.mdx | 5 + chapters/it/chapter5/8.mdx | 5 + chapters/it/chapter8/1.mdx | 5 + 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/4.mdx | 5 + chapters/ja/chapter4/5.mdx | 5 + chapters/ja/chapter4/6.mdx | 5 + chapters/ja/chapter7/1.mdx | 5 + chapters/ja/chapter7/8.mdx | 5 + chapters/ja/chapter7/9.mdx | 5 + chapters/ja/chapter8/1.mdx | 5 + 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/4.mdx | 5 + chapters/ko/chapter1/5.mdx | 5 + chapters/ko/chapter1/6.mdx | 5 + chapters/ko/chapter1/7.mdx | 5 + 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/4.mdx | 5 + chapters/pt/chapter1/5.mdx | 5 + chapters/pt/chapter1/6.mdx | 5 + chapters/pt/chapter1/7.mdx | 5 + chapters/pt/chapter1/9.mdx | 5 + chapters/pt/chapter2/1.mdx | 5 + chapters/pt/chapter2/7.mdx | 5 + chapters/pt/chapter2/8.mdx | 5 + chapters/pt/chapter4/1.mdx | 5 + 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/7.mdx | 5 + chapters/pt/chapter5/8.mdx | 5 + chapters/pt/chapter6/1.mdx | 5 + chapters/pt/chapter7/1.mdx | 5 + chapters/pt/chapter8/1.mdx | 5 + chapters/ru/chapter1/1.mdx | 5 + chapters/ru/chapter1/2.mdx | 5 + 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/9.mdx | 5 + chapters/ru/chapter2/1.mdx | 5 + chapters/ru/chapter2/7.mdx | 5 + chapters/ru/chapter3/1.mdx | 5 + chapters/ru/chapter3/5.mdx | 5 + chapters/ru/chapter3/6.mdx | 5 + chapters/ru/chapter4/1.mdx | 5 + 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/4.mdx | 5 + chapters/th/chapter1/5.mdx | 5 + chapters/th/chapter1/6.mdx | 5 + chapters/th/chapter1/7.mdx | 5 + chapters/th/chapter1/9.mdx | 5 + chapters/th/chapter2/1.mdx | 5 + chapters/th/chapter2/7.mdx | 5 + chapters/th/chapter2/8.mdx | 5 + chapters/th/chapter3/1.mdx | 5 + chapters/th/chapter3/5.mdx | 5 + chapters/th/chapter3/6.mdx | 5 + chapters/th/chapter4/1.mdx | 5 + 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/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/4.mdx | 5 + chapters/vi/chapter1/5.mdx | 5 + chapters/vi/chapter1/6.mdx | 5 + chapters/vi/chapter1/7.mdx | 5 + chapters/vi/chapter1/9.mdx | 5 + chapters/vi/chapter2/1.mdx | 5 + chapters/vi/chapter2/7.mdx | 5 + chapters/vi/chapter2/8.mdx | 5 + chapters/vi/chapter3/1.mdx | 5 + chapters/vi/chapter3/5.mdx | 5 + chapters/vi/chapter3/6.mdx | 5 + chapters/vi/chapter4/1.mdx | 5 + 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/7.mdx | 5 + chapters/vi/chapter5/8.mdx | 5 + chapters/vi/chapter6/1.mdx | 5 + chapters/vi/chapter6/10.md | 5 + 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/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/9.mdx | 25 +- chapters/zh-CN/chapter2/1.mdx | 39 +- 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/5.mdx | 45 ++- chapters/zh-CN/chapter3/6.mdx | 571 ++++++++++++++-------------- chapters/zh-CN/chapter4/1.mdx | 5 + 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/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/9.mdx | 5 + 244 files changed, 5448 insertions(+), 4228 deletions(-) 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/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/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/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/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/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/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/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/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/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/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/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 30009fb54..0bb98fca8 100644 --- a/chapters/en/chapter9/9.mdx +++ b/chapters/en/chapter9/9.mdx @@ -1,234 +1,239 @@ - - -# End-of-chapter quiz - -Let's test what you learned in this chapter! - -### 1. What can you use Gradio to do? - -share=True parameter in the launch method, you can generate a share link to send to anyone.", - correct: true - }, - { - text: "Debug your model", - explain: "One advantage of a gradio demo is being able to test your model with real data which you can change and observe the model's predictions change in real time, helping you debug your model.", - correct: true - }, - { - text: "Train your model", - explain: "Gradio is designed to be used for model inference, AFTER your model is trained.", - } - ]} -/> - -### 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? - -gr.Interface.load() method", - correct: true - } - ]} -/> - -### 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. - - + +# End-of-chapter quiz + + + +Let's test what you learned in this chapter! + +### 1. What can you use Gradio to do? + +share=True parameter in the launch method, you can generate a share link to send to anyone.", + correct: true + }, + { + text: "Debug your model", + explain: "One advantage of a gradio demo is being able to test your model with real data which you can change and observe the model's predictions change in real time, helping you debug your model.", + correct: true + }, + { + text: "Train your model", + explain: "Gradio is designed to be used for model inference, AFTER your model is trained.", + } + ]} +/> + +### 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? + +gr.Interface.load() method", + correct: true + } + ]} +/> + +### 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/8.mdx b/chapters/fr/chapter9/8.mdx index d9898c938..307e0c813 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -1,18 +1,23 @@ -# 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 ! - -## Où aller ensuite ? - -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. +# 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 ! + +## Où aller ensuite ? + +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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/9.mdx b/chapters/vi/chapter1/9.mdx index fffa994b6..baced2d1b 100644 --- a/chapters/vi/chapter1/9.mdx +++ b/chapters/vi/chapter1/9.mdx @@ -1,5 +1,10 @@ # 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: 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/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/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/4.mdx b/chapters/vi/chapter4/4.mdx index 57b703e36..a990e2d7e 100644 --- a/chapters/vi/chapter4/4.mdx +++ b/chapters/vi/chapter4/4.mdx @@ -1,5 +1,10 @@ # 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. 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/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/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/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/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/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/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/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/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/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 @@ # 标记器,回顾! + + 完成这一章,辛苦了! 在深入研究标记器之后,您应该: From 358f7cc7561d5bd18f811ad9f8d82779e665380b Mon Sep 17 00:00:00 2001 From: Mishig Davaadorj Date: Tue, 23 Aug 2022 23:45:36 +0200 Subject: [PATCH 373/789] DocNotebookDropdown -> CourseFloatingBanner --- 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 | 2102 ++++++++++++------------- 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 | 370 ++--- 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 | 762 ++++----- chapters/fr/chapter1/8.mdx | 68 +- 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/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/chapter4/2.mdx | 194 +-- chapters/fr/chapter4/3.mdx | 1276 ++++++++-------- 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/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/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/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/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/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/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/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/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 | 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/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/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 +- 233 files changed, 21878 insertions(+), 21878 deletions(-) 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/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/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/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/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/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/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/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/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/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/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 5f2ee7dd9..bd829ec95 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,186 +1,186 @@ -# 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. - -## How to create an Interface - -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"`. - - `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 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. - -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. -So let's build an `Interface` that works with audio. - -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, -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 -"Speak here...". - -In addition, we'd like to receive the audio as a numpy array so that we can easily -"reverse" it. So we'll set the `"type"` to be `"numpy"`, which passes the input -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 -shortcut `"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() -``` - -The code above will produce an interface like the one below (if your browser doesn't -ask you for microphone permissions, 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 - -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. - -The key here is that when you pass: -* a list of input components, each component corresponds to a parameter in order. -* a list of output coponents, each component corresponds to a returned value. - -The code snippet below shows how three input components line up with the three arguments of the `generate_tone()` function: - -```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() -``` - - - - -### The `launch()` method - -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 -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. - -You can customize the behavior of `launch()` through different parameters: - - - `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! - -We'll cover the `share` parameter in a lot more detail in the next section! - -## ✏️ Let's 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. - -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 -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() -``` - -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 -by passing in the `optional` parameter as `True`, we allow the user to either -provide a microphone or an audio file (or neither, but that will return an error message). - +# 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. + +## How to create an Interface + +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"`. + - `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 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. + +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. +So let's build an `Interface` that works with audio. + +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, +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 +"Speak here...". + +In addition, we'd like to receive the audio as a numpy array so that we can easily +"reverse" it. So we'll set the `"type"` to be `"numpy"`, which passes the input +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 +shortcut `"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() +``` + +The code above will produce an interface like the one below (if your browser doesn't +ask you for microphone permissions, 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 + +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. + +The key here is that when you pass: +* a list of input components, each component corresponds to a parameter in order. +* a list of output coponents, each component corresponds to a returned value. + +The code snippet below shows how three input components line up with the three arguments of the `generate_tone()` function: + +```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() +``` + + + + +### The `launch()` method + +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 +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. + +You can customize the behavior of `launch()` through different parameters: + + - `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! + +We'll cover the `share` parameter in a lot more detail in the next section! + +## ✏️ Let's 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. + +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 +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() +``` + +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 +by passing in the `optional` parameter as `True`, we allow the user to either +provide a microphone or an audio file (or neither, but that will return an error message). + Keep going to see how to share your interface with others! \ No newline at end of file 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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ế - {: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/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/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'} - 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/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/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/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/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/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/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/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/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 @@ # 逐块地构建标记器 - From d0c1d6a267cf9f40b975187dbac3d50d42235462 Mon Sep 17 00:00:00 2001 From: Acciaro Gennaro Daniele Date: Fri, 2 Sep 2022 10:31:04 +0200 Subject: [PATCH 374/789] Italian translation Ch 2/1, 2/2 (#300) --- chapters/it/_toctree.yml | 9 +- chapters/it/chapter2/1.mdx | 21 +++ chapters/it/chapter2/2.mdx | 351 +++++++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 chapters/it/chapter2/1.mdx create mode 100644 chapters/it/chapter2/2.mdx 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/chapter2/1.mdx b/chapters/it/chapter2/1.mdx new file mode 100644 index 000000000..0e5078935 --- /dev/null +++ b/chapters/it/chapter2/1.mdx @@ -0,0 +1,21 @@ +# 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! + From e2f25972fd8c0f025bb243a5de4fd7d04ccd7180 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Sep 2022 10:47:52 +0200 Subject: [PATCH 375/789] Add contributors (#304) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d209b9c5..49654517c 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ 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) | -| [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) | -| [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) | +| [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) | From 0c00f58b5de1900d33ed3aca38e9a1feff8ca8d9 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Sep 2022 11:23:08 +0200 Subject: [PATCH 376/789] Add forum button (#306) --- chapters/it/chapter2/1.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chapters/it/chapter2/1.mdx b/chapters/it/chapter2/1.mdx index 0e5078935..d0579a712 100644 --- a/chapters/it/chapter2/1.mdx +++ b/chapters/it/chapter2/1.mdx @@ -1,5 +1,10 @@ # 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. From ececc022c270cf031b1a3e9fdb2c2125e9909518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93ng=20H=E1=BA=A1nh?= Date: Fri, 2 Sep 2022 18:27:55 +0700 Subject: [PATCH 377/789] Translate into Vietnamese chap [6-9] - DONE (#303) --- chapters/vi/_toctree.yml | 65 ++ chapters/vi/chapter6/10.md | 283 ------ chapters/vi/chapter6/10.mdx | 278 ++++++ chapters/vi/chapter6/3.md | 473 --------- chapters/vi/chapter6/3.mdx | 473 +++++++++ chapters/vi/chapter6/{3b.md => 3b.mdx} | 118 +-- chapters/vi/chapter6/4.md | 123 --- chapters/vi/chapter6/4.mdx | 122 +++ chapters/vi/chapter6/5.md | 360 ------- chapters/vi/chapter6/5.mdx | 361 +++++++ chapters/vi/chapter6/6.md | 374 -------- chapters/vi/chapter6/6.mdx | 374 ++++++++ chapters/vi/chapter6/7.md | 381 -------- chapters/vi/chapter6/7.mdx | 380 ++++++++ chapters/vi/chapter6/8.md | 565 ----------- 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 +++++ 42 files changed, 12560 insertions(+), 2618 deletions(-) delete mode 100644 chapters/vi/chapter6/10.md create mode 100644 chapters/vi/chapter6/10.mdx delete mode 100644 chapters/vi/chapter6/3.md create mode 100644 chapters/vi/chapter6/3.mdx rename chapters/vi/chapter6/{3b.md => 3b.mdx} (50%) delete mode 100644 chapters/vi/chapter6/4.md create mode 100644 chapters/vi/chapter6/4.mdx delete mode 100644 chapters/vi/chapter6/5.md create mode 100644 chapters/vi/chapter6/5.mdx delete mode 100644 chapters/vi/chapter6/6.md create mode 100644 chapters/vi/chapter6/6.mdx delete mode 100644 chapters/vi/chapter6/7.md create mode 100644 chapters/vi/chapter6/7.mdx delete mode 100644 chapters/vi/chapter6/8.md 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.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/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.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 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.md b/chapters/vi/chapter6/3b.mdx similarity index 50% rename from chapters/vi/chapter6/3b.md rename to chapters/vi/chapter6/3b.mdx index c0f64d63f..ad2cf1c98 100644 --- a/chapters/vi/chapter6/3b.md +++ b/chapters/vi/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# Tokenizer nhanh trong pipeline QA +# Fast tokenizers in the QA pipeline {#if fw === 'pt'} @@ -22,7 +22,7 @@ {/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. +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'} @@ -34,9 +34,9 @@ We will now dive into the `question-answering` pipeline and see how to leverage {/if} -## Using the `question-answering` pipeline +## Sử dụng pipeline `question-answering` -As we saw in [Chapter 1](/course/chapter1), we can use the `question-answering` pipeline like this to get the answer to a question: +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 @@ -57,7 +57,7 @@ question_answerer(question=question, context=context) '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: +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 = """ @@ -107,11 +107,11 @@ question_answerer(question=question, context=long_context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -Let's see how it does all of this! +Hãy cùng nhau xem nó làm thế nào! -## Using a model for question answering +## Sử dụng mô hình cho tác vụ hỏi đáp -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)): +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'} @@ -141,14 +141,14 @@ outputs = model(**inputs) {/if} -Note that we tokenize the question and the context as a pair, with the question first. +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
-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: +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 @@ -170,9 +170,9 @@ torch.Size([1, 66]) torch.Size([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. +Để 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. -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`: +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'} @@ -180,9 +180,9 @@ Since we will apply a softmax afterward, we just need to replace the logits we w import torch sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# Che tất cả mọi thứ trừ token của ngữ cảnh mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# Hiển thị token [CLS] mask[0] = False mask = torch.tensor(mask)[None] @@ -196,9 +196,9 @@ end_logits[mask] = -10000 import tensorflow as tf sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# Che tất cả mọi thứ trừ token của ngữ cảnh mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# Hiển thị token [CLS] mask[0] = False mask = tf.constant(mask)[None] @@ -208,7 +208,7 @@ 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: +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'} @@ -226,15 +226,15 @@ 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. +Ở 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. -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: +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}]$$ -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`. +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`. -First let's compute all the possible products: +Đầ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, :] @@ -242,7 +242,7 @@ 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: +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) @@ -250,7 +250,7 @@ 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: +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 @@ -260,7 +260,7 @@ 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`: +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() @@ -269,7 +269,7 @@ 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): +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 @@ -277,11 +277,11 @@ We're not quite done yet, but at least we already have the correct score for the -✏️ **Try it out!** Compute the start and end indices for the five most likely answers. +✏️ **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. -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: +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) @@ -292,7 +292,7 @@ _, end_char = offsets[end_index] answer = context[start_char:end_char] ``` -Now we just have to format everything to get our result: +Bây giờ chúng ta chỉ cần định dạng mọi thứ để có được kết quả: ```py result = { @@ -311,17 +311,17 @@ print(result) 'score': 0.97773} ``` -Great! That's the same as in our first example! +Tuyệt quá! Kết quả đó giống như trong ví dụ đầu tiên của chúng ta! -✏️ **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. +✏️ **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ó. -## Handling long contexts +## Xử lý các ngữ cảnh dài -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): +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) @@ -332,7 +332,7 @@ print(len(inputs["input_ids"])) 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: +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") @@ -375,9 +375,9 @@ Why should I use transformers? """ ``` -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. +Đ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. -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: +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." @@ -399,9 +399,9 @@ for ids in inputs["input_ids"]: '[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. +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. -Let's take a closer look at the result of the tokenization: +Hãy cùng nhìn kĩ hơn vào kết quả tokenize: ```py print(inputs.keys()) @@ -411,7 +411,7 @@ print(inputs.keys()) 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: +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"]) @@ -421,7 +421,7 @@ print(inputs["overflow_to_sample_mapping"]) [0, 0, 0, 0, 0, 0, 0] ``` -This is more useful when we tokenize several sentences together. For instance, this: +Điều này hữu ích hơn khi ta tokenize nhiều câu cùng nhau, Ví dụ: ```py sentences = [ @@ -435,15 +435,15 @@ inputs = tokenizer( print(inputs["overflow_to_sample_mapping"]) ``` -gets us: +trả cho ta: ```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. +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. -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: +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( @@ -458,7 +458,7 @@ inputs = tokenizer( ) ``` -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: +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'} @@ -490,7 +490,7 @@ print(inputs["input_ids"].shape) {/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: +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) @@ -514,17 +514,17 @@ torch.Size([2, 384]) torch.Size([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): +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() -# Mask everything apart from the tokens of the context +# Che tất cả mọi thứ trừ token của ngữ cảnh mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# Hiển thị token [CLS] mask[0] = False -# Mask all the [PAD] tokens +# Che tất cả token [PAD] mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) start_logits[mask] = -10000 @@ -535,11 +535,11 @@ end_logits[mask] = -10000 ```py sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# Che tất cả mọi thứ trừ token của ngữ cảnh mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# Hiển thị token [CLS] mask[0] = False -# Mask all the [PAD] tokens +# 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) @@ -548,7 +548,7 @@ end_logits = tf.where(mask, -10000, end_logits) {/if} -Then we can use the softmax to convert our logits to probabilities: +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'} @@ -566,7 +566,7 @@ 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: +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'} @@ -606,15 +606,15 @@ print(candidates) [(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). +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). -✏️ **Try it out!** Adapt the code above to return the scores and spans for the five most likely answers (in total, not per chunk). +✏️ **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). -The `offsets` we grabbed earlier is actually a list of offsets, with one list per chunk of text: +`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): @@ -631,12 +631,12 @@ for candidate, offset in zip(candidates, offsets): {'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! +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! -✏️ **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. +✏️ **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ó. -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. +Đ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.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 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.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 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.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 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.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 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.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 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 8f1ba9ab01487805af88b0b4a654232ca88f51ed Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 2 Sep 2022 14:39:58 +0200 Subject: [PATCH 378/789] Italian translation: Chapter 3 (#283) --- 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 2c0b3ba499e2cbe4affd586ddc36f8759425ed47 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 13 Sep 2022 10:58:58 +0200 Subject: [PATCH 379/789] Enable notebooks to be generated in any language (#311) * Enable notebooks to be generated in any language --- README.md | 4 +- chapters/fr/chapter9/4.mdx | 21 ++++---- utils/generate_notebooks.py | 97 ++++++++++++++++++++----------------- 3 files changed, 64 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 49654517c..53e5a199b 100644 --- a/README.md +++ b/README.md @@ -122,10 +122,10 @@ python -m pip install -r requirements.txt Then run the following script: ```bash -python utils/generate_notebooks.py --output_dir nbs +python utils/generate_notebooks.py --language LANG-ID --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 associated with `chapters/LANG-ID` and stores them as notebooks in the `nbs` folder (which is ignored by Git by default). ## ✍️ Contributing a new chapter diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index d929ef20b..9a41fa572 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -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/utils/generate_notebooks.py b/utils/generate_notebooks.py index bd41c5dcb..a8844bb21 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*$") @@ -128,57 +126,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 +194,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 +233,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 +252,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), ) @@ -263,7 +269,8 @@ def create_notebooks(output_dir): if __name__ == "__main__": parser = argparse.ArgumentParser() + parser.add_argument("--language", type=str, default="en", help="Path to the course MDX files") parser.add_argument("--output_dir", type=str, help="Where to output the notebooks") args = parser.parse_args() - create_notebooks(args.output_dir) + create_notebooks(args.language, args.output_dir) From 5536b4ad106d9495b5fdd8bd2a05ed992d54fabb Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 13 Sep 2022 13:55:11 +0200 Subject: [PATCH 380/789] Generate notebooks for all languages (#313) --- README.md | 4 ++-- utils/generate_notebooks.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 53e5a199b..facab3706 100644 --- a/README.md +++ b/README.md @@ -122,10 +122,10 @@ python -m pip install -r requirements.txt Then run the following script: ```bash -python utils/generate_notebooks.py --language LANG-ID --output_dir nbs +python utils/generate_notebooks.py --output_dir nbs ``` -This script extracts all the code snippets from the chapters associated with `chapters/LANG-ID` 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/utils/generate_notebooks.py b/utils/generate_notebooks.py index a8844bb21..781fb19b3 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -20,6 +20,8 @@ frameworks = {"pt": "PyTorch", "tf": "TensorFlow"} +PATH_TO_COURSE = Path("chapters/") + def read_and_split_frameworks(fname): """ @@ -269,8 +271,14 @@ def create_notebooks(language, output_dir): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("--language", type=str, default="en", help="Path to the course MDX files") parser.add_argument("--output_dir", type=str, help="Where to output the notebooks") args = parser.parse_args() - create_notebooks(args.language, 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 796f41da59c7b358b28d5c1f3c7e5c3b47f8ae33 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 13 Sep 2022 14:29:55 +0200 Subject: [PATCH 381/789] Update notebook links (#312) * Update URLs to per language --- 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 | 4 ++-- 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/2.mdx | 4 ++-- chapters/vi/chapter6/3.mdx | 8 ++++---- chapters/vi/chapter6/3b.mdx | 8 ++++---- chapters/vi/chapter6/4.mdx | 4 ++-- chapters/vi/chapter6/5.mdx | 4 ++-- chapters/vi/chapter6/6.mdx | 4 ++-- chapters/vi/chapter6/7.mdx | 4 ++-- 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 ++-- 255 files changed, 725 insertions(+), 725 deletions(-) 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 9a41fa572..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***). 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/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.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.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.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.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.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.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.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 @@ 正如我们在前几节中看到的,标记化包括几个步骤: From 7a4a0518325bd00d5a16cf8a01bc97c1a3d4d401 Mon Sep 17 00:00:00 2001 From: Partho Date: Tue, 13 Sep 2022 21:00:54 +0530 Subject: [PATCH 382/789] minor change to avoid confusion in ch2-5 (#310) * minor change to avoid confusion * added suggestion --- chapters/en/chapter2/5.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0b89f2dc1d3923551bef63b401fdd9932d23849d Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 19 Sep 2022 15:37:41 +0200 Subject: [PATCH 383/789] Refactor tokenization of targets for transformers v4.22 (#316) * Refactor tokenization of targets for transformers v4.22 --- chapters/en/chapter7/4.mdx | 51 ++++++++++++++------------------------ chapters/en/chapter7/5.mdx | 16 ++++++------ 2 files changed, 26 insertions(+), 41 deletions(-) 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: From bb7fde4d9f13c06a33deb37db3ae8f1d52b8eee9 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Fri, 23 Sep 2022 20:42:29 +0530 Subject: [PATCH 384/789] typo fix (#319) line no.49 Changed _SQuAD_it-text.json_-> _SQuAD_it-test.json_ --- chapters/en/chapter5/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 611a24e52a8855bdef3191e992971883e84a485f Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Fri, 23 Sep 2022 17:14:03 +0200 Subject: [PATCH 385/789] [FR] Many corrections (#318) --- 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 ++-- 16 files changed, 71 insertions(+), 71 deletions(-) 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 e4a5c2d1e79a724a34cd93e3dbade1dcf6e37d94 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 3 Oct 2022 16:00:53 +0200 Subject: [PATCH 386/789] Fix URL to the Pile (#324) * Fix URL to the Pile --- chapters/en/chapter5/4.mdx | 10 +++++----- chapters/fr/chapter5/4.mdx | 10 +++++----- chapters/it/chapter5/4.mdx | 10 +++++----- chapters/pt/chapter5/4.mdx | 10 +++++----- chapters/vi/chapter5/4.mdx | 10 +++++----- chapters/zh-CN/chapter5/4.mdx | 10 +++++----- 6 files changed, 30 insertions(+), 30 deletions(-) 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/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/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/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/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/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", From 3b6e294f3ccbd63403c8cc4b60bf5ab7d1269371 Mon Sep 17 00:00:00 2001 From: Pavel <60391448+pdumin@users.noreply.github.com> Date: Mon, 3 Oct 2022 19:06:21 +0500 Subject: [PATCH 387/789] [RU] ch5 (#317) --- chapters/ru/_toctree.yml | 24 +- 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 +++++++++++++ 10 files changed, 2283 insertions(+), 1 deletion(-) 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 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/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()`. From d7cd6933a97704ac98e6dfa19072bd5777c2627a Mon Sep 17 00:00:00 2001 From: buti1021 Date: Mon, 3 Oct 2022 10:13:24 -0400 Subject: [PATCH 388/789] fix: book url (#323) --- chapters/bn/chapter1/1.mdx | 4 ++-- chapters/en/chapter1/1.mdx | 4 ++-- chapters/en/event/1.mdx | 2 +- chapters/es/chapter1/1.mdx | 4 ++-- chapters/fa/chapter1/1.mdx | 4 ++-- chapters/fr/chapter1/1.mdx | 4 ++-- chapters/fr/event/1.mdx | 2 +- chapters/hi/chapter1/1.mdx | 4 ++-- chapters/it/chapter1/1.mdx | 4 ++-- chapters/ja/chapter1/1.mdx | 4 ++-- chapters/ja/event/1.mdx | 2 +- chapters/ko/chapter1/1.mdx | 4 ++-- chapters/pt/chapter1/1.mdx | 4 ++-- chapters/pt/event/1.mdx | 2 +- chapters/ru/chapter1/1.mdx | 4 ++-- chapters/th/chapter1/1.mdx | 4 ++-- chapters/tr/chapter1/1.mdx | 4 ++-- chapters/vi/chapter1/1.mdx | 4 ++-- chapters/vi/event/1.mdx | 2 +- chapters/zh-CN/chapter1/1.mdx | 4 ++-- 20 files changed, 35 insertions(+), 35 deletions(-) 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/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/event/1.mdx b/chapters/en/event/1.mdx index d202a9ceb..2b02ed3cb 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 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/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/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/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/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/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/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/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/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/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/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任务 From 1b3e60a5dc52de1757bacf839bbf951ed8863d1b Mon Sep 17 00:00:00 2001 From: 1375626371 <40328311+1375626371@users.noreply.github.com> Date: Tue, 4 Oct 2022 19:03:14 +0800 Subject: [PATCH 389/789] zh-CN - Chapter 7,8,9finished (#315) Co-authored-by: Lewis Tunstall --- chapters/zh-CN/_toctree.yml | 79 +- chapters/zh-CN/chapter7/1.mdx | 33 + chapters/zh-CN/chapter7/2.mdx | 978 ++++++++++++++++++++++++ chapters/zh-CN/chapter7/3.mdx | 1042 +++++++++++++++++++++++++ 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 ++++ 28 files changed, 9988 insertions(+), 4 deletions(-) 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/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/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..ced0bc325 --- /dev/null +++ b/chapters/zh-CN/chapter7/3.mdx @@ -0,0 +1,1042 @@ + + +# 微调掩码语言模型 + +{#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 + + 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} + +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 2a61a648ae1f9cc6e36d0a3970a174ffe5bfe566 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 4 Oct 2022 13:42:28 +0200 Subject: [PATCH 390/789] Refactor events (#261) --- chapters/en/_toctree.yml | 10 +++-- chapters/en/chapter9/1.mdx | 8 +--- chapters/en/events/1.mdx | 49 +++++++++++++++++++++++ chapters/en/{event/1.mdx => events/2.mdx} | 0 chapters/en/events/3.mdx | 9 +++++ 5 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 chapters/en/events/1.mdx rename chapters/en/{event/1.mdx => events/2.mdx} (100%) create mode 100644 chapters/en/events/3.mdx 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/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 100% rename from chapters/en/event/1.mdx rename to chapters/en/events/2.mdx 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** + + From 4cd2c0e83c6c12d31e225c1e5bd5b686a8d014f8 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 4 Oct 2022 14:09:40 +0200 Subject: [PATCH 391/789] Fix whole word masking labels (#326) --- chapters/en/chapter7/3.mdx | 2 ++ 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 ++ 5 files changed, 10 insertions(+) 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/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/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/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/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index ced0bc325..d95d76313 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/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) ``` From 85019bcb8c2a448160e981d4b48254d5ef6d5f74 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 4 Oct 2022 14:52:46 +0200 Subject: [PATCH 392/789] Fix question answering indices (#327) --- chapters/en/chapter6/3b.mdx | 8 ++++---- chapters/fr/chapter6/3b.mdx | 8 ++++---- chapters/th/chapter6/3b.mdx | 8 ++++---- chapters/vi/chapter6/3b.mdx | 8 ++++---- chapters/zh-CN/chapter6/3b.mdx | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) 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/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/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/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/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)) From 32b2bdaead83f171d362ff711ac2e5e710c93baf Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 5 Oct 2022 09:25:07 +0200 Subject: [PATCH 393/789] Add translation checker (#329) --- README.md | 4 +-- 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/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 ++++++++++++++++++++ 10 files changed, 44 insertions(+), 10 deletions(-) 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..49549bf89 100644 --- a/README.md +++ b/README.md @@ -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/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/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 c163e9ca0d121dd8c086099ec05b4f48206da44c Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:14:52 +0200 Subject: [PATCH 394/789] [FR] Refactor events (#330) --- 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 + 4 files changed, 234 insertions(+), 172 deletions(-) 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 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** + + From 288108f96aefa5049142d549b59db66e715eb10c Mon Sep 17 00:00:00 2001 From: Fabrizio Damicelli <40115969+fabridamicelli@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:20:07 +0200 Subject: [PATCH 395/789] Translation Chapter 4 (#325) --- 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 +- 5 files changed, 762 insertions(+), 1 deletion(-) create mode 100644 chapters/de/chapter4/1.mdx create mode 100644 chapters/de/chapter4/2.mdx create mode 100644 chapters/de/chapter4/3.mdx 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 From 304a52204a535c7dbab919c6f1a425c80a255979 Mon Sep 17 00:00:00 2001 From: Fabrizio Damicelli <40115969+fabridamicelli@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:35:00 +0200 Subject: [PATCH 396/789] update author list (de) (#331) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49549bf89..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) | From 0ed19693d84caace3a5151f2d8854c352f48bcd3 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 5 Oct 2022 15:11:13 +0200 Subject: [PATCH 397/789] Fix Russian ToC (#332) --- chapters/ru/_toctree.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 9f65007921ea40ec3c41574a47dd9186e63e08e2 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 7 Oct 2022 17:12:00 +0200 Subject: [PATCH 398/789] Refactor dataset upload in Chapter 5 / section 5 (#334) --- 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 448547b5b9b8a3904843d2208da279efd73995e0 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 10 Oct 2022 10:48:40 +0200 Subject: [PATCH 399/789] Fix id2label types (#337) --- chapters/en/chapter7/2.mdx | 4 ++-- chapters/fr/chapter7/2.mdx | 4 ++-- chapters/ja/chapter7/2.mdx | 4 ++-- chapters/vi/chapter7/2.mdx | 4 ++-- chapters/zh-CN/chapter7/2.mdx | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) 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/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()} ``` From 68ca3e8c2c7da07154b8bd4805df20a057428685 Mon Sep 17 00:00:00 2001 From: Jesper Dramsch Date: Mon, 10 Oct 2022 16:24:59 +0200 Subject: [PATCH 400/789] Fix keywords in de quiz chapter 3 (#338) Noticed two `undefined` in the new render, because the `text` key was capitalized. --- chapters/de/chapter3/6.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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} From 271ec19da8469f82754952935b8c334761756a4f Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 12 Oct 2022 13:23:27 +0200 Subject: [PATCH 401/789] Tweak course validator (#340) --- utils/validate_translation.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 b68c438971ba945a29dc803c89307979354099d8 Mon Sep 17 00:00:00 2001 From: Acciaro Gennaro Daniele Date: Wed, 12 Oct 2022 13:43:10 +0200 Subject: [PATCH 402/789] [Italian] Added Ch2/3 and Ch2/4 (#322) --- chapters/it/_toctree.yml | 7 +- chapters/it/chapter2/3.mdx | 231 +++++++++++++++++++++++++++++++++++ chapters/it/chapter2/4.mdx | 240 +++++++++++++++++++++++++++++++++++++ 3 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 chapters/it/chapter2/3.mdx create mode 100644 chapters/it/chapter2/4.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 92b0283b4..7a98832cb 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -30,8 +30,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 +105,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/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. From 6e652fe43b9db288c9df467209bf7ec4e0a2b8ac Mon Sep 17 00:00:00 2001 From: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:06:18 +0100 Subject: [PATCH 403/789] Completes chapter 1 (#341) --- chapters/it/_toctree.yml | 3 + chapters/it/chapter1/10.mdx | 260 ++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 chapters/it/chapter1/10.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 7a98832cb..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: 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? + + From da4f6965c029e418ed3ac735014a0995e5ba565a Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:15:44 +0900 Subject: [PATCH 404/789] Create 5.mdx and translate it into Japanese. --- chapters/ja/chapter1/5.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 chapters/ja/chapter1/5.mdx diff --git a/chapters/ja/chapter1/5.mdx b/chapters/ja/chapter1/5.mdx new file mode 100644 index 000000000..ed6bb9948 --- /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) From 97cd7529d046e1b968b93600649531b1924464eb Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:37:38 +0900 Subject: [PATCH 405/789] Create 6.mdx and translate it into Japanese. --- chapters/ja/chapter1/6.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 chapters/ja/chapter1/6.mdx 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) From fabab7a358be619a94ef880c0a1eb454eb13fca0 Mon Sep 17 00:00:00 2001 From: blackdoor571 Date: Fri, 21 Oct 2022 19:58:52 +0900 Subject: [PATCH 406/789] done chapter1.2,1.3 --- chapters/ja/chapter1/2.mdx | 41 ++++ chapters/ja/chapter1/3.mdx | 418 +++++++++++++++++++++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 chapters/ja/chapter1/2.mdx create mode 100644 chapters/ja/chapter1/3.mdx diff --git a/chapters/ja/chapter1/2.mdx b/chapters/ja/chapter1/2.mdx new file mode 100644 index 000000000..4d94d2771 --- /dev/null +++ b/chapters/ja/chapter1/2.mdx @@ -0,0 +1,41 @@ +# 自然言語処理 / NLP(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. + +Transformerモデルの詳細に飛び込んでいく前に、自然言語処理とはどんなもので、かつ、なぜ我々が注目する必要があるのかの大まかな概要を知っていきましょう。 + +## What is NLP? 自然言語処理とはどんなもの? + +NLP is a field of linguistics and machine learning focused on understanding everything related to human language. The aim of NLP tasks is not only to understand single words individually, but to be able to understand the context of those words. + +自然言語処理とは、人の言語に関連した全てのことへの理解に焦点を当てた、言語学と機械学習の分野です。自然言語処理タスクの目標は、文章を個別に一単語ずつ理解するだけでなく、それらの単語で構成された文章の文脈を理解することです。 + +The following is a list of common NLP tasks, with some examples of each: + +以下のリストで、具体例付きで一般的な自然言語処理タスクを紹介します。 + +- **Classifying whole sentences**: Getting the sentiment of a review, detecting if an email is spam, determining if a sentence is grammatically correct or whether two sentences are logically related or not +- **Classifying each word in a sentence**: Identifying the grammatical components of a sentence (noun, verb, adjective), or the named entities (person, location, organization) +- **Generating text content**: Completing a prompt with auto-generated text, filling in the blanks in a text with masked words +- **Extracting an answer from a text**: Given a question and a context, extracting the answer to the question based on the information provided in the context +- **Generating a new sentence from an input text**: Translating a text into another language, summarizing a text + +- **文章の分類**:レビューの評価、スパムメールの検出、文法的に正しいかどうかの判断、2つの文が論理的に関連しているかどうかの判断 +- **文の中の単語分類**:品詞(名詞、動詞、形容詞)や、固有表現(人、場所、組織)の識別 +- **文章内容の生成**:自動生成されたテキストによる入力テキストの補完、文章の穴埋め +- **文章からの情報抽出**:質問と文脈が与えられたときの、文脈からの情報に基づいた質問に対する答えの抽出 +- **文章の変換**:ある文章の他の言語への翻訳、文章の要約 + +NLP isn't limited to written text though. It also tackles complex challenges in speech recognition and computer vision, such as generating a transcript of an audio sample or a description of an image. + +更に、自然言語処理は文章に限ったものではありません。音声認識やコンピュータビジョンの分野でも、音声サンプルの書き起こしや画像の説明文の生成など、複雑な課題に取り組んでいます。 + +## Why is it challenging? なぜ自然言語処理は困難なのか? +Computers don't process information in the same way as humans. For example, when we read the sentence "I am hungry," we can easily understand its meaning. Similarly, given two sentences such as "I am hungry" and "I am sad," we're able to easily determine how similar they are. For machine learning (ML) models, such tasks are more difficult. The text needs to be processed in a way that enables the model to learn from it. And because language is complex, we need to think carefully about how this processing must be done. There has been a lot of research done on how to represent text, and we will look at some methods in the next chapter. + +コンピュータは人間と同じように情報を処理するわけではありません。例えば、「私はお腹が空いています。」という文章を読むと、その意味を簡単に理解することができます。同様に、「私はお腹が空いています。」と「私は悲しいです。」という2つの文章があれば、その類似性を簡単に判断することができます。しかし、機械学習(ML)モデルにおいては、このようなタスクはより困難です。機械学習モデルが学習できるように、テキストを処理する必要があります。また、言語は複雑なため、どのように処理すべきかを慎重に考える必要があります。テキストをどのように表現するかについては多くの研究がなされており、次の章ではいくつかの方法について見ていきます。 diff --git a/chapters/ja/chapter1/3.mdx b/chapters/ja/chapter1/3.mdx new file mode 100644 index 000000000..ef6f7c218 --- /dev/null +++ b/chapters/ja/chapter1/3.mdx @@ -0,0 +1,418 @@ +# Transformers, what can they do? Transformers, 何ができる? + + + +In this section, we will look at what Transformer models can do and use our first tool from the 🤗 Transformers library: the `pipeline()` function. + +このセクションでは、Transformerモデルができることを見ていき、🤗 Transformersライブラリの最初のツールとして `pipeline()` 関数を使ってみましょう。 + + +👀 See that Open in Colab button on the top right? Click on it to open a Google Colab notebook with all the code samples of this section. This button will be present in any section containing code examples. + +If you want to run the examples locally, we recommend taking a look at the setup. + +👀 右上にOpen in Colabというボタンがありますよね?それをクリックすると、このセクションのすべてのコードサンプルを含むGoogle Colabノートブックが開きます。このボタンは、コードサンプルを含むどのセクションにも存在します。 + +ローカルでサンプルを実行したい場合は、セットアップを参照することをお勧めします。 + + +## Transformers are everywhere! Transformersは至るところに! + +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: + +Transformerモデルは前節で述べたようなあらゆる種類のNLPタスクを解決するために使用されています。ここでは、Hugging FaceとTransformerモデルを使用している企業や組織を紹介します。それらの組織はまた、モデルを共有することでコミュニティに還元しています。 + +Companies using Hugging Face + +The [🤗 Transformers library](https://github.com/huggingface/transformers) provides the functionality to create and use those shared models. The [Model Hub](https://huggingface.co/models) contains thousands of pretrained models that anyone can download and use. You can also upload your own models to the Hub! + +[🤗 Transformers library](https://github.com/huggingface/transformers)は、それらの共有モデルを作成し、使用するための機能を提供します。[Model Hub](https://huggingface.co/models)には、誰でもダウンロードして使用できる何千もの事前学習済みモデルが含まれています。また、あなた自身のモデルをModel Hubにアップロードすることも可能です。 + + +⚠️ The Hugging Face Hub is not limited to Transformer models. Anyone can share any kind of models or datasets they want! Create a huggingface.co account to benefit from all available features! + + +⚠️Hugging Face Hubはトランスフォーマーモデルに限定されるものではありません。誰でも好きな種類のモデルやデータセットを共有することができます!すべての利用可能な機能の恩恵を受けるためにhuggingface.coのアカウントを作成しましょう! + +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. + +Transformerのモデルがどのように機能するのかを知る前に、いくつかの興味深いNLPの問題を解決するため、Transformerがどのように使われるのか、いくつかの例で見ていきましょう。 + +## Working with pipelines pipelinesを使ったタスク + + + +The most basic object in the 🤗 Transformers library is the `pipeline()` function. It connects a model with its necessary preprocessing and postprocessing steps, allowing us to directly input any text and get an intelligible answer: + +🤗 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}] +``` + +We can even pass several sentences! + +複数の文章を入力することも可能です。 + +```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}] +``` + +By default, this pipeline selects a particular pretrained model that has been fine-tuned for sentiment analysis in English. The model is downloaded and cached when you create the `classifier` object. If you rerun the command, the cached model will be used instead and there is no need to download the model again. + +デフォルトでは、このpipelineは英語の感情分析用にファインチューニングされた特定の事前学習モデルを使用します。このモデルは `classifier` オブジェクトを作成する際にダウンロードされ、キャッシュされます。コマンドを再実行すると、キャッシュされたモデルが代わりに使用され、モデルを再度ダウンロードする必要はありません。 + + +There are three main steps involved when you pass some text to a pipeline: + +1. The text is preprocessed into a format the model can understand. +2. The preprocessed inputs are passed to the model. +3. The predictions of the model are post-processed, so you can make sense of them. + + +pipelineにテキストを渡す場合、主に3つのステップがあります。 + +1. テキストはモデルが理解できる形式に前処理される。 +2. 前処理された入力がモデルに渡される。 +3. 予測結果を理解できるように、モデルの後処理が行われる。 + + +Some of the currently [available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html) are: + +- `feature-extraction` (get the vector representation of a text) +- `fill-mask` +- `ner` (named entity recognition) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Let's have a look at a few of these! + +現在[利用可能な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 ゼロショット分類 + +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. + +まず、ラベル付けされていないテキストを分類する必要があるような、より困難なタスクに取り組むことから始めます。これは実際のプロジェクトでよくあるシナリオです。なぜなら、テキストにアノテーションをつけるのは通常時間がかかり、専門知識が必要だからです。このような場合、`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]} +``` + +This pipeline is called _zero-shot_ because you don't need to fine-tune the model on your data to use it. It can directly return probability scores for any list of labels you want! + +このpipelineは _zero-shot_ と呼ばれる。なぜなら、これを使うために自前のデータセットでモデルのファインチューニングをする必要がないからです。任意のラベルのリストに対して直接確率スコアを返すことができます。 + + + +✏️ **Try it out!** Play around with your own sequences and labels and see how the model behaves. + +✏️ **試してみよう!** 独自の入力とラベルで遊んでみて、モデルがどのように振る舞うか見てみましょう。 + + + +## 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. + +では、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'}] +``` + +You can control how many different sequences are generated with the argument `num_return_sequences` and the total length of the output text with the argument `max_length`. + +引数`num_return_sequences`で異なるシーケンスの生成数を、引数`max_length`で出力テキストの合計の長さを制御することができます。 + + + +✏️ **Try it out!** Use the `num_return_sequences` and `max_length` arguments to generate two sentences of 15 words each. + +✏️ **試してみよう!** `num_return_sequences` と `max_length` 引数を用いて、15語ずつの2つの文を生成することができます。 + + + + +## Using any model from the Hub in a pipeline pipelineでHubから任意のモデルを使用する + +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). + +Let's try the [`distilgpt2`](https://huggingface.co/distilgpt2) model! Here's how to load it in the same pipeline as before: + +これまでの例では、タスクに応じたデフォルトのモデルを使用しましたが、特定のタスク(例えばテキスト生成)の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'}] +``` + +You can refine your search for a model by clicking on the language tags, and pick a model that will generate text in another language. The Model Hub even contains checkpoints for multilingual models that support several languages. + +Once you select a model by clicking on it, you'll see that there is a widget enabling you to try it directly online. This way you can quickly test the model's capabilities before downloading it. + +言語タグをクリックして検索するモデルを絞り込み、他の言語でテキストを生成するモデルを選ぶことができます。Model Hubには、複数の言語をサポートする多言語モデルのチェックポイントもあります。 + +モデルをクリックで選択すると、オンラインで直接試用できるウィジェットが表示されます。このようにして、ダウンロードする前にモデルの機能をすばやくテストすることができます。 + + + +✏️ **Try it out!** Use the filters to find a text generation model for another language. Feel free to play with the widget and use it in a pipeline! + +✏️ **試してみよう!** フィルタ-を使って、他の言語のテキスト生成モデルを探してみましょう。ウィジェットで自由に遊んだり、pipelineで使ってみてください! + + + + +### The Inference API 推論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. + +すべてのモデルは、Hugging Face [ウェブサイト](https://huggingface.co/)で公開されているInference APIを使って、ブラウザから直接テストすることが可能です。このページでは、カスタムテキストを入力し、モデルが入力データを処理する様子を見ることで、直接モデルで遊ぶことができます。 + +このウィジェットを動かすInference APIは、有料製品としても提供されており、ワークフローに必要な場合は便利です。詳しくは[価格ページ](https://huggingface.co/pricing)をご覧ください。 + +## 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: + +次に試す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'}] +``` + +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* と呼ばれます。他の空所穴埋めモデルは異なるマスクトークンを持つかもしれないので、他のモデルを探索するときには常に適切なマスクワードを確認するのが良いでしょう。それを確認する1つの方法は、ウィジェットで使用されているマスクワードを見ることです。 + + + +✏️ **Try it out!** Search for the `bert-base-cased` model on the Hub and identify its mask word in the Inference API widget. What does this model predict for the sentence in our `pipeline` example above? + +✏️ **試してみよう!** Hub で `bert-base-cased` モデルを検索し、推論API ウィジェットでそのマスクワードを特定します。このモデルは上記の `pipeline` の例文に対して何を予測するでしょうか? + + + +## 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: + +固有表現認識(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} +] +``` + +Here the model correctly identified that Sylvain is a person (PER), Hugging Face an organization (ORG), and Brooklyn a location (LOC). + +We pass the option `grouped_entities=True` in the pipeline creation function to tell the pipeline to regroup together the parts of the sentence that correspond to the same entity: here the model correctly grouped "Hugging" and "Face" as a single organization, even though the name consists of multiple words. In fact, as we will see in the next chapter, the preprocessing even splits some words into smaller parts. For instance, `Sylvain` is split into four pieces: `S`, `##yl`, `##va`, and `##in`. In the post-processing step, the pipeline successfully regrouped those pieces. + +ここでは、モデルはSylvainが人(PER)、Hugging Faceが組織(ORG)、Brooklynが場所(LOC)であることを正しく識別しています。 + +pipelineの作成機能でオプション `grouped_entities=True` を渡すと、同じエンティティに対応する文の部分を再グループ化するようpipelineに指示します。ここでは、名前が複数の単語で構成されていても、モデルは "Hugging" と "Face" を一つの組織として正しくグループ化しています。実際、次の章で説明するように、前処理ではいくつかの単語をより小さなパーツに分割することさえあります。例えば、`Sylvain`は4つの部分に分割されます。`S`, `##yl`, `##va`, and `##in`.です。後処理の段階で、pipelineはこれらの断片をうまく再グループ化しました。 + + + +✏️ **Try it out!** Search the Model Hub for a model able to do part-of-speech tagging (usually abbreviated as POS) in English. What does this model predict for the sentence in the example above? + +✏️ **試してみよう!** Model Hubで英語の品詞タグ付け(通常POSと略される)を行えるモデルを検索してください。このモデルは、上の例の文に対して何を予測するでしょうか? + + + +## Question answering 質問応答 + +The `question-answering` pipeline answers questions using information from a given context: + +質問応答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'} +``` + +Note that this pipeline works by extracting information from the provided context; it does not generate the answer. + +このpipelineは、提供されたコンテキストから情報を抽出することで動作し、答えを生成するわけではないことに注意してください。 + +## 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: + +要約とは、文章中の重要な部分をすべて(あるいはほとんど)維持したまま、より短い文章にするタスクです。以下はその例です。 + +```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 .'}] +``` + +Like with text generation, you can specify a `max_length` or a `min_length` for the result. + +テキスト生成と同様に、結果に対して `max_length` や `min_length` を指定することができます。 + + +## 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: + +翻訳の場合、タスク名に言語ペアを指定すれば(`"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.'}] +``` + +Like with text generation and summarization, you can specify a `max_length` or a `min_length` for the result. + +テキスト生成や要約と同様に、結果に対して `max_length` や `min_length` を指定することができます。 + + + +✏️ **Try it out!** Search for translation models in other languages and try to translate the previous sentence into a few different languages. + +✏️ **試してみよう!** 他言語の翻訳モデルを検索して、前の文章をいくつかの異なる言語に翻訳してみましょう。 + + + +The pipelines shown so far are mostly for demonstrative purposes. They were programmed for specific tasks and cannot perform variations of them. In the next chapter, you'll learn what's inside a `pipeline()` function and how to customize its behavior. + +これまで紹介したpipelineは、ほとんどがデモンストレーションのためのものです。これらは特定のタスクのためにプログラムされたものであり、それらのバリエーションを実行することはできません。次の章では、`pipeline()`関数の中身と、その動作をカスタマイズする方法を学びます。 From 2d455148bd9ee2edee10ced49ecb5a960f8b2dae Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Sun, 23 Oct 2022 13:04:51 +0900 Subject: [PATCH 407/789] Create 4.mdx and translate it into Japanese. --- chapters/ja/chapter1/4.mdx | 281 +++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 chapters/ja/chapter1/4.mdx diff --git a/chapters/ja/chapter1/4.mdx b/chapters/ja/chapter1/4.mdx new file mode 100644 index 000000000..ddb78688f --- /dev/null +++ b/chapters/ja/chapter1/4.mdx @@ -0,0 +1,281 @@ +# How do Transformers work? Transformersの仕組みについて + + + +In this section, we will take a high-level look at the architecture of Transformer models. + +このセクションでは、Transformerモデルのアーキテクチャをざっくりと見ていきます。 + +## A bit of Transformer history Transformerの歴史を簡単に + +Here are some reference points in the (short) history of Transformer models: + +Transformerモデルの(短い)歴史の中で、参考となるポイントをいくつか紹介します。 + +
+A brief chronology of Transformers models. + +
+ +The [Transformer architecture](https://arxiv.org/abs/1706.03762) was introduced in June 2017. The focus of the original research was on translation tasks. This was followed by the introduction of several influential models, including: + +[Transformerのアーキテクチャ](https://arxiv.org/abs/1706.03762)は2017年6月に登場しました。 当初の研究は翻訳タスクに焦点を置いていましたが、これに続くようにして以下のような影響力のあるモデルがいくつか登場します。 + +- **June 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), the first pretrained Transformer model, used for fine-tuning on various NLP tasks and obtained state-of-the-art results + +- **October 2018**: [BERT](https://arxiv.org/abs/1810.04805), another large pretrained model, this one designed to produce better summaries of sentences (more on this in the next chapter!) + +- **February 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), an improved (and bigger) version of GPT that was not immediately publicly released due to ethical concerns + +- **October 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), a distilled version of BERT that is 60% faster, 40% lighter in memory, and still retains 97% of BERT's performance + +- **October 2019**: [BART](https://arxiv.org/abs/1910.13461) and [T5](https://arxiv.org/abs/1910.10683), two large pretrained models using the same architecture as the original Transformer model (the first to do so) + +- **May 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), an even bigger version of GPT-2 that is able to perform well on a variety of tasks without the need for fine-tuning (called _zero-shot learning_) + + +- **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学習_)で様々なタスクを解くことができるようにしたモデルです。 + + +This list is far from comprehensive, and is just meant to highlight a few of the different kinds of Transformer models. Broadly, they can be grouped into three categories: + +このリストは決して包括的なものではなく、Transformerのモデルの種類をざっくり分けることを意図しています。種類については大きく以下の3つのカテゴリーに分類することができます。 + +- GPT-like (also called _auto-regressive_ Transformer models) +- BERT-like (also called _auto-encoding_ Transformer models) +- BART/T5-like (also called _sequence-to-sequence_ Transformer models) + + +- GPT型 (_auto-regressive_ Transformerモデルとも呼ばれます) +- BERT型 (_auto-encoding_ Transformerモデルとも呼ばれます) +- BART/T5型 (_sequence-to-sequence_ Transformerモデルとも呼ばれます) + +We will dive into these families in more depth later on. + +これらの種類についてこれから深掘りしていきます。 + +## Transformers are language models Transformers = 言語モデル + +All the Transformer models mentioned above (GPT, BERT, BART, T5, etc.) have been trained as *language models*. This means they have been trained on large amounts of raw text in a self-supervised fashion. Self-supervised learning is a type of training in which the objective is automatically computed from the inputs of the model. That means that humans are not needed to label the data! + +GPT, BERT, T5などの上記の全てのモデルは*言語モデル*として学習されています。これは大量の生文に対して自己教師あり学習を行ったことを意味しています。自己教師あり学習は、学習の目的となるものを、モデルに入力するデータから自動で算出する学習方法です。つまりデータに対する人手のラベル付が必要ないことを意味します。 + +This type of model develops a statistical understanding of the language it has been trained on, but it's not very useful for specific practical tasks. Because of this, the general pretrained model then goes through a process called *transfer learning*. During this process, the model is fine-tuned in a supervised way -- that is, using human-annotated labels -- on a given task. + +このタイプのモデルは、学習させた言語に対する統計的な理解を深めることができますが、特定のタスクにはあまり役に立ちません。従って、一般的な事前学習済みモデルは、この後に*転移学習*と呼ばれるプロセスを経ます。このプロセスでは人手でラベル付されたデータを用いた教師あり学習を行なって、特定のタスクに対してfine-tuningされます。 + +An example of a task is predicting the next word in a sentence having read the *n* previous words. This is called *causal language modeling* because the output depends on the past and present inputs, but not the future ones. + +タスクの例の1つに、前のいくつかの単語を読んで、それに続く次の単語を予測するものがあります。これは出力が過去と現在の入力にのみ依存し、将来の入力には依存しないため、 *Causal Language Modeling (CLM)* と呼ばれます。 + +
+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. + +他の例としては *Masked Language Modeling (MLM)* があり、これは文中のマスクされた(隠された)単語が何かを予測するタスクになっています。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformers are big models Transformers = 大規模モデル + +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. + +前述のDistilBERTなどの例外を除けば、より良いパフォーマンスを達成するための一般的な戦略として、モデルサイズと学習データ量を大きくするというものがあります。 + +
+Number of parameters of recent Transformers models +
+ +Unfortunately, training a model, especially a large one, requires a large amount of data. This becomes very costly in terms of time and compute resources. It even translates to environmental impact, as can be seen in the following graph. + +残念ながらモデルの学習(特に大規模なモデルの学習)には大量のデータが必要になります。これは時間と計算資源の面で非常にコストがかかります。また、以下のグラフから分かるように、環境にも影響を及ぼすものになります。 + +
+The carbon footprint of a large language model. + +
+ + + +And this is showing a project for a (very big) model led by a team consciously trying to reduce the environmental impact of pretraining. The footprint of running lots of trials to get the best hyperparameters would be even higher. + +そしてこの図は、事前学習の環境負荷を意識的に減らすことを目的とするチームが率いる、(超大規模)モデルのプロジェクトを示しています。最適なハイパーパラメータを得るための多くの試行による環境負荷は、より大きなものになると考えられます。 + +Imagine if each time a research team, a student organization, or a company wanted to train a model, it did so from scratch. This would lead to huge, unnecessary global costs! + +もし研究チームや学生団体、企業がその度にモデルを一から学習していたらどうでしょうか。これでは膨大で不必要なコストがかかってしまいます。 + +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 転移学習 + + + +*Pretraining* is the act of training a model from scratch: the weights are randomly initialized, and the training starts without any prior knowledge. + +*事前学習*とはモデルを一から学習することです。重みはランダムに初期化され、事前知識なしに学習が開始されます。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +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: + +一方で*ファインチューニング*は事前学習の**後に**行われるものです。ファインチューニングを行うには、まず最初に事前学習済みモデルを取得し、次にタスクに応じたデータセットを用いて追加の学習を行います。ここで、「(事前学習を行わずに)初めからこのタスクに対して学習を行えば良いのでは?」と思った方がいるかもしれませんが、これにはいくつかの理由があります。 + +* 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. +* For the same reason, the amount of time and resources needed to get good results are much lower. + +* 事前学習済みモデルは、ファインチューニング用のデータセットと何らかの類似性を持ったデータで既に学習が行われています。このため、ファインチューニングの過程において、事前学習済みモデルが既に獲得した知識を利用することができます。(例えば自然言語処理の問題では、事前学習済みのモデルは言語に対する何らかの統計的な理解をしているはずです。) +* また事前学習済みモデルは大量のデータを使って学習されているので、ファインチューニングでははるかに少ないデータで適切な結果を得ることが可能になります。 +* これと同じ理由で、良い結果を得るために必要な時間や資源を大きく削減することができます。 + +For example, one could leverage a pretrained model trained on the English language and then fine-tune it on an arXiv corpus, resulting in a science/research-based model. The fine-tuning will only require a limited amount of data: the knowledge the pretrained model has acquired is "transferred," hence the term *transfer learning*. + +例えば、英語で訓練された事前学習済みモデルをarXivコーパスでファインチューニングすることで、科学/研究ベースのモデルを作ることができます。ファインチューニングは少ないデータで実施できます。これは事前学習済みモデルが獲得していた知識が「転移」しているためで、この特徴から「*転移学習*」と呼ばれているという訳です。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Fine-tuning a model therefore has lower time, data, financial, and environmental costs. It is also quicker and easier to iterate over different fine-tuning schemes, as the training is less constraining than a full pretraining. + +従って、モデルのファインチューニングに必要な時間、データ、経済的/環境的コストは少なく済みます。また事前学習よりも制約が少ないため、様々なファインチューニングのスキームを素早く簡単に試すことができます。 + +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 一般的なアーキテクチャ + +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. + +このセクションでは、Transformerモデルの一般的なアーキテクチャについて見ていきます。各構成要素については後ほど詳しく説明するので、理解できない部分があっても心配ありません! + + + +## Introduction 導入 + +The model is primarily composed of two blocks: + +モデルは主に2つの要素で構成されます。 + +* **Encoder (left)**: The encoder receives an input and builds a representation of it (its features). This means that the model is optimized to acquire understanding from the input. +* **Decoder (right)**: The decoder uses the encoder's representation (features) along with other inputs to generate a target sequence. This means that the model is optimized for generating outputs. + +* **エンコーダー (左)**: エンコーダーは入力を受け取り、その特徴量を生成します。これは入力から理解を得るためにモデルが最適化されることを意味します。 +* **デコーダー (右)**: デコーダーではんエンコーダーが生成した特徴量とその他の入力を受け取って、目的の系列を生成します。これは出力を生成するためにモデルが最適化されることを意味します。 + +
+Architecture of a Transformers models + +
+ +Each of these parts can be used independently, depending on the task: + +これらの構成要素はタスクに応じてそれぞれ別々に使用することができます。 + +* **Encoder-only models**: Good for tasks that require understanding of the input, such as sentence classification and named entity recognition. +* **Decoder-only models**: Good for generative tasks such as text generation. +* **Encoder-decoder models** or **sequence-to-sequence models**: Good for generative tasks that require an input, such as translation or summarization. + +* **Encoder-only モデル**: 文章分類や固有表現抽出など入力に対する理解が必要となるタスクに適しています。 +* **Decoder-only モデル**: 文生成などの生成タスクに適しています。 +* **Encoder-Decoder(sequence-to-sequence) モデル**: 翻訳や要約など、入力を要する生成タスクに適しています。 + +We will dive into those architectures independently in later sections. + +これらのアーキテクチャについてはのちのセクションで個別に紹介します。 + + +## 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. + +Transformerモデルは*attention層*と呼ばれる特殊な層で構築されていることが大きな特徴となっています。実際にTransformerが登場した論文のタイトルも["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)というものでした! アテンション層については後ほど詳しく説明します。現段階においては、モデルが各単語の特徴量を扱う際に、入力されたテキストのどの単語に注目すべきかをアテンション層が指示してくれる(多かれ少なかれその他の単語は無視される)ということだけ知っておいてもらえれば十分です。 + +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. + +このことを理解するために、英語からフランス語への翻訳タスクを考えてみます。"You like this course" という入力があるとき、翻訳モデルは "like" という単語を適切に翻訳するために "You" という隣接する単語に注目する必要があります。これはフランス語の動詞 "like" は主語によって異なる活用がされるためです。ただこのとき、"like" の翻訳に他の単語の情報は役に立ちません。同じように、モデルは "this" という単語を翻訳する際に "course" という単語に注意を払う必要があり、これは "this" という単語の翻訳が関連する名詞が男性か女性かによって変化するためです。この場合においてもその他の単語は "this" の翻訳には関係ありません。より複雑な文(および文法規則)では、モデルは各単語を適切に翻訳するために、文中のより離れた位置に出現する可能性のある単語に対して特別な注意を払う必要があります。 + +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. + +単語はそれ自体で意味を持ちますが、その意味は文脈(前後に現れるその他の単語)に大きな影響を受けます。このため、翻訳タスクと同じ考え方が自然言語に関する色々なタスクに対して当てはまります。 + +Now that you have an idea of what attention layers are all about, let's take a closer look at the Transformer architecture. + +さて、アテンション層がどのようなものかを理解頂いた上で、Transformerのアーキテクチャをより詳しく見ていきましょう! + +## 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. + +Transformerのアーキテクチャは翻訳用に設計されました。学習過程において、エンコーダーはある言語の入力(文章)を受け取り、デコーダーは別言語で書かれた同じ文章を受け取ります。エンコーダーのアテンション層は文中nの全ての単語を使うことができます。(先ほど見たように、、ある単語を翻訳するためにはその前後の単語に注意を払う必要があるためです。)一方でデコーダーは逐次的に動作します。このため既に翻訳して生成した単語にしか注意を向けることができません。(言い換えればこれから翻訳して生成される単語に対しては注意が張れないということです。)例えば、翻訳対象の最初の3単語を予測したらそれをデコーダーに渡すことで、デコーダーはエンコーダーに入力された情報を全て使いながら4単語目を予測します。 + +To speed things up during training (when the model has access to target sentences), the decoder is fed the whole target, but it is not allowed to use future words (if it had access to the word at position 2 when trying to predict the word at position 2, the problem would not be very hard!). For instance, when trying to predict the fourth word, the attention layer will only have access to the words in positions 1 to 3. + +モデルの学習中、学習速度を上げるためにデコーダーには答えとなる翻訳文(ターゲット文)全体が与えられていますが、処理対象となる単語の後に続く単語を使うことは許されていません。例えば、4番目の単語を予測する際、アテンション層はターゲット文の1〜3番目の位置にある単語にしかアクセスすることができません。 + +The original Transformer architecture looked like this, with the encoder on the left and the decoder on the right: + +Transformerのオリジナルのアーキテクチャの概観は、このように左側のエンコーダーと右側のデコーダーからなります。 + +
+Architecture of a Transformers models + +
+ +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. + +デコーダーブロックの最初のアテンション層は、デコーダーに対する全ての入力を使うことができますが、2番目のアテンション層はエンコーダーの出力を利用します。従って、入力文全体にアクセスすることで現在の単語の最適な予測が可能になるという訳です。これは言語によって単語の登場順が異なるような文法規則があったり、文の後半で提供される文脈情報が、現在の単語の翻訳に役立つ場合があるので、非常に便利なものとなっています。 + +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. + +*attentionマスク*はエンコーダー・デコーダーで、ある特別な単語に注目しないようにするために使用されます。(例えば、文をまとめて入力するときに、全ての文を同じ長さに揃えるために使われるpadding tokenなどです。) + +## Architectures vs. checkpoints アーキテクチャ vs. チェックポイント + +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: + +このコースでTransformerモデルについて掘り下げていくと、*モデル*と同様に*アーキテクチャ*や*チェックポイント*という単語についても言及されていることがわかります。これらの用語はそれぞれ少しずつ異なる意味を持っています。 + +* **Architecture**: This is the skeleton of the model -- the definition of each layer and each operation that happens within the model. +* **Checkpoints**: These are the weights that will be loaded in a given architecture. +* **Model**: This is an umbrella term that isn't as precise as "architecture" or "checkpoint": it can mean both. This course will specify *architecture* or *checkpoint* when it matters to reduce ambiguity. + + +* **アーキテクチャ**: これはモデルの骨格を意味し、モデル内の各層と内部で起こる操作を定義したものになります。 +* **チェックポイント**: これは与えられたアーキテクチャに対して読み込まれる重みを意味します。 +* **モデル**: これは「アーキテクチャ」や「チェックポイント」ほど正確ではない、より包括的な用語で両方を意味することがあります。このコースでは曖昧さを回避するために、重要な場合は*アーキテクチャ*や*チェックポイント*を使うこととします。 + +For example, BERT is an architecture while `bert-base-cased`, a set of weights trained by the Google team for the first release of BERT, is a checkpoint. However, one can say "the BERT model" and "the `bert-base-cased` model." + +例えばBERTはアーキテクチャを指し、`bert-base-cased`はGoogleの開発チームがBERTの最初のリリースのために用意した重みを指したチェックポイントとなります。しかしながら"BERTモデル"や"`bert-base-cased`モデル"と呼ぶこともできます。 From cd419a212253927633d08ba0a2c16341bccc17a2 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:21:41 +0900 Subject: [PATCH 408/789] Slightly modified --- chapters/ja/chapter1/4.mdx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/chapters/ja/chapter1/4.mdx b/chapters/ja/chapter1/4.mdx index ddb78688f..e13210540 100644 --- a/chapters/ja/chapter1/4.mdx +++ b/chapters/ja/chapter1/4.mdx @@ -1,4 +1,4 @@ -# How do Transformers work? Transformersの仕組みについて +# How do Transformers work? / Transformersの仕組みについて Example of causal language modeling in which the next word from a sentence is predicted. @@ -95,7 +95,7 @@ Another example is *masked language modeling*, in which the model predicts a mas
-## Transformers are big models Transformers = 大規模モデル +## Transformers are big models / Transformers = 大規模モデル 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. @@ -128,7 +128,7 @@ This is why sharing language models is paramount: sharing the trained weights an 従って、学習済み言語モデルの重みを共有しそれを利用することで、コミュニティ全体の計算コストや環境負荷を削減することができるのです。 -## Transfer Learning 転移学習 +## Transfer Learning / 転移学習 @@ -174,7 +174,7 @@ This process will also achieve better results than training from scratch (unless このプロセスは(大量のデータがある場合を除いて)ゼロから学習するよりも良い結果をもたらします。だからこそ(目的のタスクにできるだけ近い)事前学習済みモデルを活用し、それをファインチューニングするべきだと言えます。 -## 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. @@ -182,7 +182,7 @@ In this section, we'll go over the general architecture of the Transformer model -## Introduction 導入 +## Introduction / 導入 The model is primarily composed of two blocks: @@ -216,7 +216,7 @@ We will dive into those architectures independently in later sections. これらのアーキテクチャについてはのちのセクションで個別に紹介します。 -## 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. @@ -234,7 +234,7 @@ Now that you have an idea of what attention layers are all about, let's take a c さて、アテンション層がどのようなものかを理解頂いた上で、Transformerのアーキテクチャをより詳しく見ていきましょう! -## 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. @@ -261,7 +261,7 @@ The *attention mask* can also be used in the encoder/decoder to prevent the mode *attentionマスク*はエンコーダー・デコーダーで、ある特別な単語に注目しないようにするために使用されます。(例えば、文をまとめて入力するときに、全ての文を同じ長さに揃えるために使われるpadding tokenなどです。) -## Architectures vs. checkpoints アーキテクチャ vs. チェックポイント +## Architectures vs. checkpoints / アーキテクチャ vs. チェックポイント 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: From 6fcb7958252df5a49a58be1867a36c3679bda709 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:22:58 +0900 Subject: [PATCH 409/789] Slightly modified --- chapters/ja/chapter1/5.mdx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/chapters/ja/chapter1/5.mdx b/chapters/ja/chapter1/5.mdx index ed6bb9948..53b83bd71 100644 --- a/chapters/ja/chapter1/5.mdx +++ b/chapters/ja/chapter1/5.mdx @@ -1,12 +1,20 @@ -# エンコーダーモデル +# 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*. + +The pretraining of these models usually revolves around somehow corrupting a given sentence (for instance, by masking random words in it) and tasking the model with finding or reconstructing the initial sentence. + +Encoder models are best suited for tasks requiring an understanding of the full sentence, such as sentence classification, named entity recognition (and more generally word classification), and extractive question answering. + +Representatives of this family of models include: + エンコーダーモデルとは、Transformerモデルのエンコーダーのみを使用したモデルを指します。 処理の各段階で、attention層は最初の文の全ての単語にアクセスすることができます。 これらのモデルは "bi-directional"(双方向)のattentionを持つものとして特徴付けられ、*オートエンコーダーモデル*と呼ばれます。 これらのモデルの事前学習は、何らかの方法で(例えば文中の単語をランダムにマスクするなどで)文を壊し、この文の再構築をタスクとして解くことを中心に展開されます。 From ca7dd53ed37113efc9f6d3c6daab7f22ea49a654 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:23:50 +0900 Subject: [PATCH 410/789] Slightly modified --- chapters/ja/chapter1/6.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chapters/ja/chapter1/6.mdx b/chapters/ja/chapter1/6.mdx index 6044ae35e..5ddb4e755 100644 --- a/chapters/ja/chapter1/6.mdx +++ b/chapters/ja/chapter1/6.mdx @@ -1,4 +1,4 @@ -# デコーダーモデル +# 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*. + +The pretraining of decoder models usually revolves around predicting the next word in the sentence. + +These models are best suited for tasks involving text generation. + +Representatives of this family of models include: + デコーダーモデルとは、Transformerモデルのデコーダーのみを使用したモデルを指します。 処理の各段階で、処理対象の単語について、attention層はその単語より前に出現した単語にのみアクセスすることができます。 このようなモデルは*自己回帰モデル*と呼ばれます。  デコーダーモデルの事前学習は、次に続く単語を予測するタスクを解くことを中心に展開されます。 From c43313237e8920b962dfa45ac77a33aa3dba1322 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 28 Oct 2022 08:20:34 +0100 Subject: [PATCH 411/789] TF generation fixes (#344) * Fixes to chapter 7 Co-authored-by: lewtun --- chapters/en/chapter7/2.mdx | 4 ++-- chapters/en/chapter7/3.mdx | 12 +++++----- chapters/en/chapter7/4.mdx | 48 ++++++++++++++++++++++++-------------- chapters/en/chapter7/5.mdx | 43 ++++++++++++++++++++++++++-------- chapters/en/chapter7/6.mdx | 12 +++++----- chapters/en/chapter7/7.mdx | 14 ++++------- 6 files changed, 82 insertions(+), 51 deletions(-) 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..6b93c1fea 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -289,9 +289,10 @@ 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 +674,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 +728,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, From 16e0528c1ae6e0f10c4d92aac868867f2b320e6a Mon Sep 17 00:00:00 2001 From: Angel Mendez Date: Fri, 28 Oct 2022 02:31:04 -0500 Subject: [PATCH 412/789] i18n: ES - translate file chapter2/6.mdx (#346) --- chapters/es/_toctree.yml | 2 + chapters/es/chapter2/6.mdx | 164 +++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 chapters/es/chapter2/6.mdx diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 5129356c0..24519d9e3 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -33,6 +33,8 @@ title: Tokenizadores - local: chapter2/5 title: Manejando Secuencias Múltiples + - local: chapter2/6 + title: Poniendo todo junto - 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} From be7e2451ff5ee00db6a45f655c0edbe689fd944b Mon Sep 17 00:00:00 2001 From: Artem Vysotsky <420428+vood@users.noreply.github.com> Date: Fri, 28 Oct 2022 03:57:01 -0400 Subject: [PATCH 413/789] Typo in russian translation (#349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It should be "Обучающий цикл" not "Обучающий цикла" --- chapters/ru/chapter3/4.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index 388040777..ba14eae43 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` и «прикрепим» к видеокарте нашу модель и данные: From fd49e93a8e9e53ca070e0ffc1d4049bb48b24871 Mon Sep 17 00:00:00 2001 From: Artem Vysotsky <420428+vood@users.noreply.github.com> Date: Fri, 28 Oct 2022 04:08:18 -0400 Subject: [PATCH 414/789] Remove translated string (#350) --- chapters/ru/chapter3/4.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index ba14eae43..84a9bc7d8 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -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 ``` From f5d80393b3ab1389273ec4f542c265a1832bde16 Mon Sep 17 00:00:00 2001 From: Acciaro Gennaro Daniele Date: Fri, 28 Oct 2022 10:22:15 +0200 Subject: [PATCH 415/789] [It] Ch2/5, Ch2/6, Ch2/7 (#353) --- chapters/it/_toctree.yml | 6 + chapters/it/chapter2/5.mdx | 339 +++++++++++++++++++++++++++++++++++++ chapters/it/chapter2/6.mdx | 164 ++++++++++++++++++ chapters/it/chapter2/7.mdx | 18 ++ 4 files changed, 527 insertions(+) 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/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. From 3d5c4467f0f81137c6d7536c1228ba73800ee70c Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 28 Oct 2022 20:01:35 +0200 Subject: [PATCH 416/789] Add FAQ (#354) --- chapters/en/chapter1/1.mdx | 47 +++++++++++++++++++++++++++++++++++++ utils/generate_notebooks.py | 5 ++++ 2 files changed, 52 insertions(+) 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/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 d95d89fd04638125ad80def3f03c2b4d44dc9e3b Mon Sep 17 00:00:00 2001 From: Angel Mendez Date: Fri, 28 Oct 2022 13:02:05 -0500 Subject: [PATCH 417/789] i18n: ES - translate file chapter2/7.mdx (#347) --- chapters/es/_toctree.yml | 2 ++ chapters/es/chapter2/7.mdx | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 chapters/es/chapter2/7.mdx diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 24519d9e3..16c97039c 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -35,6 +35,8 @@ title: Manejando Secuencias Múltiples - local: chapter2/6 title: Poniendo todo junto + - local: chapter2/7 + title: ¡Haz completado el uso básico! - title: 3. Ajuste (fine-tuning) de un modelo preentrenado sections: 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. From ed04da0a37228a30291cd13c065dccbcfa8cc71f Mon Sep 17 00:00:00 2001 From: Gusti Adli Anshari <54521960+gstdl@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:03:13 +0700 Subject: [PATCH 418/789] [id] Add translation to Bahasa Indonesia for chapter0 & some of chapter1 (#351) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 1 + chapters/id/_toctree.yml | 11 ++ chapters/id/chapter0/1.mdx | 110 +++++++++++++++++++ chapters/id/chapter1/1.mdx | 61 ++++++++++ chapters/id/chapter1/2.mdx | 27 +++++ 7 files changed, 212 insertions(+), 2 deletions(-) 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 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/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. From fcef635cc2a041952655d752d2e667dc3348a4d8 Mon Sep 17 00:00:00 2001 From: Angel Mendez Date: Fri, 28 Oct 2022 13:40:30 -0500 Subject: [PATCH 419/789] i18n: ES - chapter2/8.mdx (#352) --- chapters/es/_toctree.yml | 2 + chapters/es/chapter2/8.mdx | 310 +++++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 chapters/es/chapter2/8.mdx diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 16c97039c..6289440b6 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -37,6 +37,8 @@ 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/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} From e1c9aa326c5b5483bbd8717569d914b0b73a732b Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Tue, 1 Nov 2022 14:47:38 +0900 Subject: [PATCH 420/789] Update 4.mdx based on the advice. --- chapters/ja/chapter1/4.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/ja/chapter1/4.mdx b/chapters/ja/chapter1/4.mdx index e13210540..b57bf7f34 100644 --- a/chapters/ja/chapter1/4.mdx +++ b/chapters/ja/chapter1/4.mdx @@ -71,7 +71,7 @@ We will dive into these families in more depth later on. All the Transformer models mentioned above (GPT, BERT, BART, T5, etc.) have been trained as *language models*. This means they have been trained on large amounts of raw text in a self-supervised fashion. Self-supervised learning is a type of training in which the objective is automatically computed from the inputs of the model. That means that humans are not needed to label the data! -GPT, BERT, T5などの上記の全てのモデルは*言語モデル*として学習されています。これは大量の生文に対して自己教師あり学習を行ったことを意味しています。自己教師あり学習は、学習の目的となるものを、モデルに入力するデータから自動で算出する学習方法です。つまりデータに対する人手のラベル付が必要ないことを意味します。 +GPT、BERT、T5などの上記の全てのモデルは*言語モデル*として学習されています。これは大量の生文に対して自己教師あり学習を行ったことを意味しています。自己教師あり学習は、学習の目的となるものを、モデルに入力するデータから自動で算出する学習方法です。つまりデータに対する人手のラベル付が必要ないことを意味します。 This type of model develops a statistical understanding of the language it has been trained on, but it's not very useful for specific practical tasks. Because of this, the general pretrained model then goes through a process called *transfer learning*. During this process, the model is fine-tuned in a supervised way -- that is, using human-annotated labels -- on a given task. @@ -192,7 +192,7 @@ The model is primarily composed of two blocks: * **Decoder (right)**: The decoder uses the encoder's representation (features) along with other inputs to generate a target sequence. This means that the model is optimized for generating outputs. * **エンコーダー (左)**: エンコーダーは入力を受け取り、その特徴量を生成します。これは入力から理解を得るためにモデルが最適化されることを意味します。 -* **デコーダー (右)**: デコーダーではんエンコーダーが生成した特徴量とその他の入力を受け取って、目的の系列を生成します。これは出力を生成するためにモデルが最適化されることを意味します。 +* **デコーダー (右)**: デコーダーではエンコーダーが生成した特徴量とその他の入力を受け取って、目的の系列を生成します。これは出力を生成するためにモデルが最適化されることを意味します。
Architecture of a Transformers models @@ -220,7 +220,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. -Transformerモデルは*attention層*と呼ばれる特殊な層で構築されていることが大きな特徴となっています。実際にTransformerが登場した論文のタイトルも["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)というものでした! アテンション層については後ほど詳しく説明します。現段階においては、モデルが各単語の特徴量を扱う際に、入力されたテキストのどの単語に注目すべきかをアテンション層が指示してくれる(多かれ少なかれその他の単語は無視される)ということだけ知っておいてもらえれば十分です。 +Transformerモデルは*アテンション層*と呼ばれる特殊な層で構築されていることが大きな特徴となっています。実際にTransformerが登場した論文のタイトルも["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)というものでした! アテンション層については後ほど詳しく説明します。現段階においては、モデルが各単語の特徴量を扱う際に、入力されたテキストのどの単語に注目すべきかをアテンション層が指示してくれる(多かれ少なかれその他の単語は無視される)ということだけ知っておいてもらえれば十分です。 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. @@ -238,7 +238,7 @@ Now that you have an idea of what attention layers are all about, let's take a c 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. -Transformerのアーキテクチャは翻訳用に設計されました。学習過程において、エンコーダーはある言語の入力(文章)を受け取り、デコーダーは別言語で書かれた同じ文章を受け取ります。エンコーダーのアテンション層は文中nの全ての単語を使うことができます。(先ほど見たように、、ある単語を翻訳するためにはその前後の単語に注意を払う必要があるためです。)一方でデコーダーは逐次的に動作します。このため既に翻訳して生成した単語にしか注意を向けることができません。(言い換えればこれから翻訳して生成される単語に対しては注意が張れないということです。)例えば、翻訳対象の最初の3単語を予測したらそれをデコーダーに渡すことで、デコーダーはエンコーダーに入力された情報を全て使いながら4単語目を予測します。 +Transformerのアーキテクチャは翻訳用に設計されました。学習過程において、エンコーダーはある言語の入力(文章)を受け取り、デコーダーは別言語で書かれた同じ文章を受け取ります。エンコーダーのアテンション層は文中の全ての単語を使うことができます。(先ほど見たように、、ある単語を翻訳するためにはその前後の単語に注意を払う必要があるためです。)一方でデコーダーは逐次的に動作します。このため既に翻訳して生成した単語にしか注意を向けることができません。(言い換えればこれから翻訳して生成される単語に対しては注意が張られないということです。)例えば、翻訳対象の最初の3単語を予測したらそれをデコーダーに渡すことで、デコーダーはエンコーダーに入力された情報を全て使いながら4単語目を予測します。 To speed things up during training (when the model has access to target sentences), the decoder is fed the whole target, but it is not allowed to use future words (if it had access to the word at position 2 when trying to predict the word at position 2, the problem would not be very hard!). For instance, when trying to predict the fourth word, the attention layer will only have access to the words in positions 1 to 3. @@ -274,7 +274,7 @@ As we dive into Transformer models in this course, you'll see mentions of *archi * **アーキテクチャ**: これはモデルの骨格を意味し、モデル内の各層と内部で起こる操作を定義したものになります。 * **チェックポイント**: これは与えられたアーキテクチャに対して読み込まれる重みを意味します。 -* **モデル**: これは「アーキテクチャ」や「チェックポイント」ほど正確ではない、より包括的な用語で両方を意味することがあります。このコースでは曖昧さを回避するために、重要な場合は*アーキテクチャ*や*チェックポイント*を使うこととします。 +* **モデル**: これは「アーキテクチャ」や「チェックポイント」ほど正確ではない、より包括的な用語で両方を意味することがあります。このコースでは曖昧さを回避するために、重要な場合は*アーキテクチャ*や*チェックポイント*を使うことにします。 For example, BERT is an architecture while `bert-base-cased`, a set of weights trained by the Google team for the first release of BERT, is a checkpoint. However, one can say "the BERT model" and "the `bert-base-cased` model." From e044813d9420cfb652a1a62aab211b646057a3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Fraa=C3=9F?= Date: Tue, 1 Nov 2022 17:19:10 +0100 Subject: [PATCH 421/789] [de] Translation Chapter 1 (#336) --- 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 ++++++++++++------- 12 files changed, 1136 insertions(+), 63 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 From 1bab3b74d985a6c316bed38f1d245ae6a1944867 Mon Sep 17 00:00:00 2001 From: Christopher Akiki Date: Wed, 2 Nov 2022 15:34:41 +0100 Subject: [PATCH 422/789] Update 1.mdx (#356) --- chapters/en/events/1.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 +
From d12ccdbab9066739756a7ef26efe7e0692213b97 Mon Sep 17 00:00:00 2001 From: Christopher Akiki Date: Wed, 2 Nov 2022 15:35:06 +0100 Subject: [PATCH 423/789] Update 1.mdx (#357) --- chapters/fr/events/1.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 97f072ead7e8f6a7d4d2e0aaea6ac1bce1d8e4c5 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:52:23 +0900 Subject: [PATCH 424/789] removed original english texts to open pull request --- chapters/ja/chapter1/4.mdx | 124 +++---------------------------------- 1 file changed, 10 insertions(+), 114 deletions(-) diff --git a/chapters/ja/chapter1/4.mdx b/chapters/ja/chapter1/4.mdx index b57bf7f34..4c9f6e585 100644 --- a/chapters/ja/chapter1/4.mdx +++ b/chapters/ja/chapter1/4.mdx @@ -1,17 +1,13 @@ -# How do Transformers work? / Transformersの仕組みについて +# Transformersの仕組みについて -In this section, we will take a high-level look at the architecture of Transformer models. - このセクションでは、Transformerモデルのアーキテクチャをざっくりと見ていきます。 -## A bit of Transformer history / Transformerの歴史を簡単に - -Here are some reference points in the (short) history of Transformer models: +## Transformerの歴史を簡単に Transformerモデルの(短い)歴史の中で、参考となるポイントをいくつか紹介します。 @@ -20,23 +16,8 @@ Transformerモデルの(短い)歴史の中で、参考となるポイント
-The [Transformer architecture](https://arxiv.org/abs/1706.03762) was introduced in June 2017. The focus of the original research was on translation tasks. This was followed by the introduction of several influential models, including: - [Transformerのアーキテクチャ](https://arxiv.org/abs/1706.03762)は2017年6月に登場しました。 当初の研究は翻訳タスクに焦点を置いていましたが、これに続くようにして以下のような影響力のあるモデルがいくつか登場します。 -- **June 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), the first pretrained Transformer model, used for fine-tuning on various NLP tasks and obtained state-of-the-art results - -- **October 2018**: [BERT](https://arxiv.org/abs/1810.04805), another large pretrained model, this one designed to produce better summaries of sentences (more on this in the next chapter!) - -- **February 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), an improved (and bigger) version of GPT that was not immediately publicly released due to ethical concerns - -- **October 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), a distilled version of BERT that is 60% faster, 40% lighter in memory, and still retains 97% of BERT's performance - -- **October 2019**: [BART](https://arxiv.org/abs/1910.13461) and [T5](https://arxiv.org/abs/1910.10683), two large pretrained models using the same architecture as the original Transformer model (the first to do so) - -- **May 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), an even bigger version of GPT-2 that is able to perform well on a variety of tasks without the need for fine-tuning (called _zero-shot learning_) - - - **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): これも大規模な事前学習済みモデルで、文についてのより良い要約を生成するように設計されています。(こちらについては次の章で詳しく説明します!) @@ -49,36 +30,20 @@ The [Transformer architecture](https://arxiv.org/abs/1706.03762) was introduced - **2020/5** [GPT-3](https://arxiv.org/abs/2005.14165): GPT-2をさらに大規模化したもので、fine-tuningなし(_zero-shot学習_)で様々なタスクを解くことができるようにしたモデルです。 - -This list is far from comprehensive, and is just meant to highlight a few of the different kinds of Transformer models. Broadly, they can be grouped into three categories: - このリストは決して包括的なものではなく、Transformerのモデルの種類をざっくり分けることを意図しています。種類については大きく以下の3つのカテゴリーに分類することができます。 -- GPT-like (also called _auto-regressive_ Transformer models) -- BERT-like (also called _auto-encoding_ Transformer models) -- BART/T5-like (also called _sequence-to-sequence_ Transformer models) - - - GPT型 (_auto-regressive_ Transformerモデルとも呼ばれます) - BERT型 (_auto-encoding_ Transformerモデルとも呼ばれます) - BART/T5型 (_sequence-to-sequence_ Transformerモデルとも呼ばれます) -We will dive into these families in more depth later on. - これらの種類についてこれから深掘りしていきます。 -## Transformers are language models / Transformers = 言語モデル - -All the Transformer models mentioned above (GPT, BERT, BART, T5, etc.) have been trained as *language models*. This means they have been trained on large amounts of raw text in a self-supervised fashion. Self-supervised learning is a type of training in which the objective is automatically computed from the inputs of the model. That means that humans are not needed to label the data! +## Transformers = 言語モデル GPT、BERT、T5などの上記の全てのモデルは*言語モデル*として学習されています。これは大量の生文に対して自己教師あり学習を行ったことを意味しています。自己教師あり学習は、学習の目的となるものを、モデルに入力するデータから自動で算出する学習方法です。つまりデータに対する人手のラベル付が必要ないことを意味します。 -This type of model develops a statistical understanding of the language it has been trained on, but it's not very useful for specific practical tasks. Because of this, the general pretrained model then goes through a process called *transfer learning*. During this process, the model is fine-tuned in a supervised way -- that is, using human-annotated labels -- on a given task. - このタイプのモデルは、学習させた言語に対する統計的な理解を深めることができますが、特定のタスクにはあまり役に立ちません。従って、一般的な事前学習済みモデルは、この後に*転移学習*と呼ばれるプロセスを経ます。このプロセスでは人手でラベル付されたデータを用いた教師あり学習を行なって、特定のタスクに対してfine-tuningされます。 -An example of a task is predicting the next word in a sentence having read the *n* previous words. This is called *causal language modeling* because the output depends on the past and present inputs, but not the future ones. - タスク例の1つに、前のいくつかの単語を読んで、それに続く次の単語を予測するものがあります。これは出力が過去と現在の入力にのみ依存し、将来の入力には依存しないため、 *Causal Language Modeling (CLM)* と呼ばれます。
@@ -86,8 +51,6 @@ An example of a task is predicting the next word in a sentence having read the *
-Another example is *masked language modeling*, in which the model predicts a masked word in the sentence. - 他の例としては *Masked Language Modeling (MLM)* があり、これは文中のマスクされた(隠された)単語が何かを予測するタスクになっています。
@@ -95,9 +58,7 @@ Another example is *masked language modeling*, in which the model predicts a mas
-## Transformers are big models / Transformers = 大規模モデル - -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. +## Transformers = 大規模モデル 前述のDistilBERTなどの例外を除けば、より良いパフォーマンスを達成するための一般的な戦略として、モデルサイズと学習データ量を大きくするというものがあります。 @@ -105,8 +66,6 @@ Apart from a few outliers (like DistilBERT), the general strategy to achieve bet Number of parameters of recent Transformers models
-Unfortunately, training a model, especially a large one, requires a large amount of data. This becomes very costly in terms of time and compute resources. It even translates to environmental impact, as can be seen in the following graph. - 残念ながらモデルの学習(特に大規模なモデルの学習)には大量のデータが必要になります。これは時間と計算資源の面で非常にコストがかかります。また、以下のグラフから分かるように、環境にも影響を及ぼすものになります。
@@ -116,24 +75,16 @@ Unfortunately, training a model, especially a large one, requires a large amount -And this is showing a project for a (very big) model led by a team consciously trying to reduce the environmental impact of pretraining. The footprint of running lots of trials to get the best hyperparameters would be even higher. - そしてこの図は、事前学習の環境負荷を意識的に減らすことを目的とするチームが率いる、(超大規模)モデルのプロジェクトを示しています。最適なハイパーパラメータを得るための多くの試行による環境負荷は、より大きなものになると考えられます。 -Imagine if each time a research team, a student organization, or a company wanted to train a model, it did so from scratch. This would lead to huge, unnecessary global costs! - もし研究チームや学生団体、企業がその度にモデルを一から学習していたらどうでしょうか。これでは膨大で不必要なコストがかかってしまいます。 -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 / 転移学習 +## 転移学習 -*Pretraining* is the act of training a model from scratch: the weights are randomly initialized, and the training starts without any prior knowledge. - *事前学習*とはモデルを一から学習することです。重みはランダムに初期化され、事前知識なしに学習が開始されます。
@@ -141,24 +92,14 @@ This is why sharing language models is paramount: sharing the trained weights an
-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: - 一方で*ファインチューニング*は事前学習の**後に**行われるものです。ファインチューニングを行うには、まず最初に事前学習済みモデルを取得し、次にタスクに応じたデータセットを用いて追加の学習を行います。ここで、「(事前学習を行わずに)初めからこのタスクに対して学習を行えば良いのでは?」と思った方がいるかもしれませんが、これにはいくつかの理由があります。 -* 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. -* For the same reason, the amount of time and resources needed to get good results are much lower. - * 事前学習済みモデルは、ファインチューニング用のデータセットと何らかの類似性を持ったデータで既に学習が行われています。このため、ファインチューニングの過程において、事前学習済みモデルが既に獲得した知識を利用することができます。(例えば自然言語処理の問題では、事前学習済みのモデルは言語に対する何らかの統計的な理解をしているはずです。) * また事前学習済みモデルは大量のデータを使って学習されているので、ファインチューニングでははるかに少ないデータで適切な結果を得ることが可能になります。 * これと同じ理由で、良い結果を得るために必要な時間や資源を大きく削減することができます。 -For example, one could leverage a pretrained model trained on the English language and then fine-tune it on an arXiv corpus, resulting in a science/research-based model. The fine-tuning will only require a limited amount of data: the knowledge the pretrained model has acquired is "transferred," hence the term *transfer learning*. - 例えば、英語で訓練された事前学習済みモデルをarXivコーパスでファインチューニングすることで、科学/研究ベースのモデルを作ることができます。ファインチューニングは少ないデータで実施できます。これは事前学習済みモデルが獲得していた知識が「転移」しているためで、この特徴から「*転移学習*」と呼ばれているという訳です。
@@ -166,31 +107,20 @@ For example, one could leverage a pretrained model trained on the English langua
-Fine-tuning a model therefore has lower time, data, financial, and environmental costs. It is also quicker and easier to iterate over different fine-tuning schemes, as the training is less constraining than a full pretraining. - 従って、モデルのファインチューニングに必要な時間、データ、経済的/環境的コストは少なく済みます。また事前学習よりも制約が少ないため、様々なファインチューニングのスキームを素早く簡単に試すことができます。 -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 / 一般的なアーキテクチャ - -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. +## 一般的なアーキテクチャ このセクションでは、Transformerモデルの一般的なアーキテクチャについて見ていきます。各構成要素については後ほど詳しく説明するので、理解できない部分があっても心配ありません! -## Introduction / 導入 - -The model is primarily composed of two blocks: +## イントロダクション モデルは主に2つの要素で構成されます。 -* **Encoder (left)**: The encoder receives an input and builds a representation of it (its features). This means that the model is optimized to acquire understanding from the input. -* **Decoder (right)**: The decoder uses the encoder's representation (features) along with other inputs to generate a target sequence. This means that the model is optimized for generating outputs. - * **エンコーダー (左)**: エンコーダーは入力を受け取り、その特徴量を生成します。これは入力から理解を得るためにモデルが最適化されることを意味します。 * **デコーダー (右)**: デコーダーではエンコーダーが生成した特徴量とその他の入力を受け取って、目的の系列を生成します。これは出力を生成するためにモデルが最適化されることを意味します。 @@ -199,53 +129,31 @@ The model is primarily composed of two blocks:
-Each of these parts can be used independently, depending on the task: - これらの構成要素はタスクに応じてそれぞれ別々に使用することができます。 -* **Encoder-only models**: Good for tasks that require understanding of the input, such as sentence classification and named entity recognition. -* **Decoder-only models**: Good for generative tasks such as text generation. -* **Encoder-decoder models** or **sequence-to-sequence models**: Good for generative tasks that require an input, such as translation or summarization. - * **Encoder-only モデル**: 文章分類や固有表現抽出など入力に対する理解が必要となるタスクに適しています。 * **Decoder-only モデル**: 文生成などの生成タスクに適しています。 * **Encoder-Decoder(sequence-to-sequence) モデル**: 翻訳や要約など、入力を要する生成タスクに適しています。 -We will dive into those architectures independently in later sections. - これらのアーキテクチャについてはのちのセクションで個別に紹介します。 -## 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. +## アテンション層 Transformerモデルは*アテンション層*と呼ばれる特殊な層で構築されていることが大きな特徴となっています。実際にTransformerが登場した論文のタイトルも["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)というものでした! アテンション層については後ほど詳しく説明します。現段階においては、モデルが各単語の特徴量を扱う際に、入力されたテキストのどの単語に注目すべきかをアテンション層が指示してくれる(多かれ少なかれその他の単語は無視される)ということだけ知っておいてもらえれば十分です。 -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. - このことを理解するために、英語からフランス語への翻訳タスクを考えてみます。"You like this course" という入力があるとき、翻訳モデルは "like" という単語を適切に翻訳するために "You" という隣接する単語に注目する必要があります。これはフランス語の動詞 "like" は主語によって異なる活用がされるためです。ただこのとき、"like" の翻訳に他の単語の情報は役に立ちません。同じように、モデルは "this" という単語を翻訳する際に "course" という単語に注意を払う必要があり、これは "this" という単語の翻訳が関連する名詞が男性か女性かによって変化するためです。この場合においてもその他の単語は "this" の翻訳には関係ありません。より複雑な文(および文法規則)では、モデルは各単語を適切に翻訳するために、文中のより離れた位置に出現する可能性のある単語に対して特別な注意を払う必要があります。 -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. - 単語はそれ自体で意味を持ちますが、その意味は文脈(前後に現れるその他の単語)に大きな影響を受けます。このため、翻訳タスクと同じ考え方が自然言語に関する色々なタスクに対して当てはまります。 -Now that you have an idea of what attention layers are all about, let's take a closer look at the Transformer architecture. - さて、アテンション層がどのようなものかを理解頂いた上で、Transformerのアーキテクチャをより詳しく見ていきましょう! -## 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. +## オリジナルのアーキテクチャ Transformerのアーキテクチャは翻訳用に設計されました。学習過程において、エンコーダーはある言語の入力(文章)を受け取り、デコーダーは別言語で書かれた同じ文章を受け取ります。エンコーダーのアテンション層は文中の全ての単語を使うことができます。(先ほど見たように、、ある単語を翻訳するためにはその前後の単語に注意を払う必要があるためです。)一方でデコーダーは逐次的に動作します。このため既に翻訳して生成した単語にしか注意を向けることができません。(言い換えればこれから翻訳して生成される単語に対しては注意が張られないということです。)例えば、翻訳対象の最初の3単語を予測したらそれをデコーダーに渡すことで、デコーダーはエンコーダーに入力された情報を全て使いながら4単語目を予測します。 -To speed things up during training (when the model has access to target sentences), the decoder is fed the whole target, but it is not allowed to use future words (if it had access to the word at position 2 when trying to predict the word at position 2, the problem would not be very hard!). For instance, when trying to predict the fourth word, the attention layer will only have access to the words in positions 1 to 3. - モデルの学習中、学習速度を上げるためにデコーダーには答えとなる翻訳文(ターゲット文)全体が与えられていますが、処理対象となる単語の後に続く単語を使うことは許されていません。例えば、4番目の単語を予測する際、アテンション層はターゲット文の1〜3番目の位置にある単語にしかアクセスすることができません。 -The original Transformer architecture looked like this, with the encoder on the left and the decoder on the right: - Transformerのオリジナルのアーキテクチャの概観は、このように左側のエンコーダーと右側のデコーダーからなります。
@@ -253,29 +161,17 @@ Transformerのオリジナルのアーキテクチャの概観は、このよう
-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. - デコーダーブロックの最初のアテンション層は、デコーダーに対する全ての入力を使うことができますが、2番目のアテンション層はエンコーダーの出力を利用します。従って、入力文全体にアクセスすることで現在の単語の最適な予測が可能になるという訳です。これは言語によって単語の登場順が異なるような文法規則があったり、文の後半で提供される文脈情報が、現在の単語の翻訳に役立つ場合があるので、非常に便利なものとなっています。 -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. - *attentionマスク*はエンコーダー・デコーダーで、ある特別な単語に注目しないようにするために使用されます。(例えば、文をまとめて入力するときに、全ての文を同じ長さに揃えるために使われるpadding tokenなどです。) -## Architectures vs. checkpoints / アーキテクチャ vs. チェックポイント - -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: +## アーキテクチャ vs. チェックポイント このコースでTransformerモデルについて掘り下げていくと、*モデル*と同様に*アーキテクチャ*や*チェックポイント*という単語についても言及されていることがわかります。これらの用語はそれぞれ少しずつ異なる意味を持っています。 -* **Architecture**: This is the skeleton of the model -- the definition of each layer and each operation that happens within the model. -* **Checkpoints**: These are the weights that will be loaded in a given architecture. -* **Model**: This is an umbrella term that isn't as precise as "architecture" or "checkpoint": it can mean both. This course will specify *architecture* or *checkpoint* when it matters to reduce ambiguity. - * **アーキテクチャ**: これはモデルの骨格を意味し、モデル内の各層と内部で起こる操作を定義したものになります。 * **チェックポイント**: これは与えられたアーキテクチャに対して読み込まれる重みを意味します。 * **モデル**: これは「アーキテクチャ」や「チェックポイント」ほど正確ではない、より包括的な用語で両方を意味することがあります。このコースでは曖昧さを回避するために、重要な場合は*アーキテクチャ*や*チェックポイント*を使うことにします。 - -For example, BERT is an architecture while `bert-base-cased`, a set of weights trained by the Google team for the first release of BERT, is a checkpoint. However, one can say "the BERT model" and "the `bert-base-cased` model." 例えばBERTはアーキテクチャを指し、`bert-base-cased`はGoogleの開発チームがBERTの最初のリリースのために用意した重みを指したチェックポイントとなります。しかしながら"BERTモデル"や"`bert-base-cased`モデル"と呼ぶこともできます。 From fed8595defe890e3805faf0bdd977106f008853c Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:52:56 +0900 Subject: [PATCH 425/789] removed original english texts to open pull request --- chapters/ja/chapter1/5.mdx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/chapters/ja/chapter1/5.mdx b/chapters/ja/chapter1/5.mdx index 53b83bd71..894cc748c 100644 --- a/chapters/ja/chapter1/5.mdx +++ b/chapters/ja/chapter1/5.mdx @@ -1,4 +1,4 @@ -# 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*. - -The pretraining of these models usually revolves around somehow corrupting a given sentence (for instance, by masking random words in it) and tasking the model with finding or reconstructing the initial sentence. - -Encoder models are best suited for tasks requiring an understanding of the full sentence, such as sentence classification, named entity recognition (and more generally word classification), and extractive question answering. - -Representatives of this family of models include: - エンコーダーモデルとは、Transformerモデルのエンコーダーのみを使用したモデルを指します。 処理の各段階で、attention層は最初の文の全ての単語にアクセスすることができます。 これらのモデルは "bi-directional"(双方向)のattentionを持つものとして特徴付けられ、*オートエンコーダーモデル*と呼ばれます。 これらのモデルの事前学習は、何らかの方法で(例えば文中の単語をランダムにマスクするなどで)文を壊し、この文の再構築をタスクとして解くことを中心に展開されます。 From 7d07be84483610094774bb2c8b2f584a830860e6 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:53:15 +0900 Subject: [PATCH 426/789] removed original english texts to open pull request --- chapters/ja/chapter1/6.mdx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/chapters/ja/chapter1/6.mdx b/chapters/ja/chapter1/6.mdx index 5ddb4e755..4a6d5783a 100644 --- a/chapters/ja/chapter1/6.mdx +++ b/chapters/ja/chapter1/6.mdx @@ -7,14 +7,6 @@ -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*. - -The pretraining of decoder models usually revolves around predicting the next word in the sentence. - -These models are best suited for tasks involving text generation. - -Representatives of this family of models include: - デコーダーモデルとは、Transformerモデルのデコーダーのみを使用したモデルを指します。 処理の各段階で、処理対象の単語について、attention層はその単語より前に出現した単語にのみアクセスすることができます。 このようなモデルは*自己回帰モデル*と呼ばれます。  デコーダーモデルの事前学習は、次に続く単語を予測するタスクを解くことを中心に展開されます。 From 9b4285892185d5c805f16c4d70c75d2670050de4 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Thu, 3 Nov 2022 22:44:17 +0900 Subject: [PATCH 427/789] add lines for chap1/4 to 6 --- chapters/ja/_toctree.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index c1d0fcd7f..fe2b5baa3 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -7,6 +7,12 @@ sections: - local: chapter1/1 title: イントロダクション + - local: chapter1/4 + title: Transformersの仕組みについて + - local: chapter1/5 + title: エンコーダーモデル + - local: chapter1/6 + title: デコーダーモデル - title: 4. モデルとトークナイザーの共有 sections: From 6a1edd948af0b014945ef5d9bd2bb729a7223187 Mon Sep 17 00:00:00 2001 From: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Date: Thu, 3 Nov 2022 22:44:36 +0900 Subject: [PATCH 428/789] Slightly modified --- chapters/ja/chapter1/6.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/ja/chapter1/6.mdx b/chapters/ja/chapter1/6.mdx index 4a6d5783a..6044ae35e 100644 --- a/chapters/ja/chapter1/6.mdx +++ b/chapters/ja/chapter1/6.mdx @@ -1,4 +1,4 @@ -# Decoder models / デコーダーモデル +# デコーダーモデル Date: Fri, 4 Nov 2022 19:00:21 +0900 Subject: [PATCH 429/789] modify 2.mdx, 3.mdx --- chapters/ja/chapter1/2.mdx | 23 ++----- chapters/ja/chapter1/3.mdx | 133 ++++++------------------------------- 2 files changed, 23 insertions(+), 133 deletions(-) diff --git a/chapters/ja/chapter1/2.mdx b/chapters/ja/chapter1/2.mdx index 4d94d2771..11fb226da 100644 --- a/chapters/ja/chapter1/2.mdx +++ b/chapters/ja/chapter1/2.mdx @@ -5,37 +5,22 @@ classNames="absolute z-10 right-0 top-0" /> -Before jumping into Transformer models, let's do a quick overview of what natural language processing is and why we care about it. - Transformerモデルの詳細に飛び込んでいく前に、自然言語処理とはどんなもので、かつ、なぜ我々が注目する必要があるのかの大まかな概要を知っていきましょう。 -## What is NLP? 自然言語処理とはどんなもの? - -NLP is a field of linguistics and machine learning focused on understanding everything related to human language. The aim of NLP tasks is not only to understand single words individually, but to be able to understand the context of those words. +## 自然言語処理とはどんなもの? 自然言語処理とは、人の言語に関連した全てのことへの理解に焦点を当てた、言語学と機械学習の分野です。自然言語処理タスクの目標は、文章を個別に一単語ずつ理解するだけでなく、それらの単語で構成された文章の文脈を理解することです。 -The following is a list of common NLP tasks, with some examples of each: - 以下のリストで、具体例付きで一般的な自然言語処理タスクを紹介します。 -- **Classifying whole sentences**: Getting the sentiment of a review, detecting if an email is spam, determining if a sentence is grammatically correct or whether two sentences are logically related or not -- **Classifying each word in a sentence**: Identifying the grammatical components of a sentence (noun, verb, adjective), or the named entities (person, location, organization) -- **Generating text content**: Completing a prompt with auto-generated text, filling in the blanks in a text with masked words -- **Extracting an answer from a text**: Given a question and a context, extracting the answer to the question based on the information provided in the context -- **Generating a new sentence from an input text**: Translating a text into another language, summarizing a text - - **文章の分類**:レビューの評価、スパムメールの検出、文法的に正しいかどうかの判断、2つの文が論理的に関連しているかどうかの判断 - **文の中の単語分類**:品詞(名詞、動詞、形容詞)や、固有表現(人、場所、組織)の識別 - **文章内容の生成**:自動生成されたテキストによる入力テキストの補完、文章の穴埋め - **文章からの情報抽出**:質問と文脈が与えられたときの、文脈からの情報に基づいた質問に対する答えの抽出 - **文章の変換**:ある文章の他の言語への翻訳、文章の要約 -NLP isn't limited to written text though. It also tackles complex challenges in speech recognition and computer vision, such as generating a transcript of an audio sample or a description of an image. - -更に、自然言語処理は文章に限ったものではありません。音声認識やコンピュータビジョンの分野でも、音声サンプルの書き起こしや画像の説明文の生成など、複雑な課題に取り組んでいます。 +さらに、自然言語処理は文章に限ったものではありません。音声認識やコンピュータビジョンの分野でも、音声サンプルの書き起こしや画像の説明文の生成など、複雑な課題に取り組んでいます。 -## Why is it challenging? なぜ自然言語処理は困難なのか? -Computers don't process information in the same way as humans. For example, when we read the sentence "I am hungry," we can easily understand its meaning. Similarly, given two sentences such as "I am hungry" and "I am sad," we're able to easily determine how similar they are. For machine learning (ML) models, such tasks are more difficult. The text needs to be processed in a way that enables the model to learn from it. And because language is complex, we need to think carefully about how this processing must be done. There has been a lot of research done on how to represent text, and we will look at some methods in the next chapter. +## なぜ自然言語処理は困難なのか? -コンピュータは人間と同じように情報を処理するわけではありません。例えば、「私はお腹が空いています。」という文章を読むと、その意味を簡単に理解することができます。同様に、「私はお腹が空いています。」と「私は悲しいです。」という2つの文章があれば、その類似性を簡単に判断することができます。しかし、機械学習(ML)モデルにおいては、このようなタスクはより困難です。機械学習モデルが学習できるように、テキストを処理する必要があります。また、言語は複雑なため、どのように処理すべきかを慎重に考える必要があります。テキストをどのように表現するかについては多くの研究がなされており、次の章ではいくつかの方法について見ていきます。 +コンピュータは人間と同じように情報を処理するわけではありません。例えば、「私はお腹が空いています。」という文章を読むと、人間はその意味を簡単に理解することができます。同様に、「私はお腹が空いています。」と「私は悲しいです。」という2つの文章があれば、その類似性を人間は簡単に判断することができます。しかし、機械学習(ML)モデルにおいては、このようなタスクはより困難です。機械学習モデルが学習できるように、テキストを処理する必要があります。また、言語は複雑なため、どのように処理すべきかを慎重に考える必要があります。テキストをどのように表現するかについては多くの研究がなされており、次の章ではいくつかの方法について見ていきます。 diff --git a/chapters/ja/chapter1/3.mdx b/chapters/ja/chapter1/3.mdx index ef6f7c218..73a205308 100644 --- a/chapters/ja/chapter1/3.mdx +++ b/chapters/ja/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Transformers, what can they do? Transformers, 何ができる? +# Transformersで何ができる? -In this section, we will look at what Transformer models can do and use our first tool from the 🤗 Transformers library: the `pipeline()` function. このセクションでは、Transformerモデルができることを見ていき、🤗 Transformersライブラリの最初のツールとして `pipeline()` 関数を使ってみましょう。 -👀 See that Open in Colab button on the top right? Click on it to open a Google Colab notebook with all the code samples of this section. This button will be present in any section containing code examples. - -If you want to run the examples locally, we recommend taking a look at the setup. - 👀 右上にOpen in Colabというボタンがありますよね?それをクリックすると、このセクションのすべてのコードサンプルを含むGoogle Colabノートブックが開きます。このボタンは、コードサンプルを含むどのセクションにも存在します。 ローカルでサンプルを実行したい場合は、セットアップを参照することをお勧めします。 -## Transformers are everywhere! Transformersは至るところに! - -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: +## Transformersは至るところに! Transformerモデルは前節で述べたようなあらゆる種類のNLPタスクを解決するために使用されています。ここでは、Hugging FaceとTransformerモデルを使用している企業や組織を紹介します。それらの組織はまた、モデルを共有することでコミュニティに還元しています。 Companies using Hugging Face -The [🤗 Transformers library](https://github.com/huggingface/transformers) provides the functionality to create and use those shared models. The [Model Hub](https://huggingface.co/models) contains thousands of pretrained models that anyone can download and use. You can also upload your own models to the Hub! - [🤗 Transformers library](https://github.com/huggingface/transformers)は、それらの共有モデルを作成し、使用するための機能を提供します。[Model Hub](https://huggingface.co/models)には、誰でもダウンロードして使用できる何千もの事前学習済みモデルが含まれています。また、あなた自身のモデルをModel Hubにアップロードすることも可能です。 -⚠️ The Hugging Face Hub is not limited to Transformer models. Anyone can share any kind of models or datasets they want! Create a huggingface.co account to benefit from all available features! - - ⚠️Hugging Face Hubはトランスフォーマーモデルに限定されるものではありません。誰でも好きな種類のモデルやデータセットを共有することができます!すべての利用可能な機能の恩恵を受けるためにhuggingface.coのアカウントを作成しましょう! + -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. +
Transformerのモデルがどのように機能するのかを知る前に、いくつかの興味深いNLPの問題を解決するため、Transformerがどのように使われるのか、いくつかの例で見ていきましょう。 -## Working with pipelines pipelinesを使ったタスク +## pipelineを使ってタスクを実行する -The most basic object in the 🤗 Transformers library is the `pipeline()` function. It connects a model with its necessary preprocessing and postprocessing steps, allowing us to directly input any text and get an intelligible answer: - 🤗 Transformers ライブラリの中で最も基本的なオブジェクトは `pipeline()` 関数です。これはモデルを必要な前処理と後処理のステップに接続し、任意のテキストを直接入力して理解しやすい答えを得ることを可能にします。 ```python @@ -61,8 +48,6 @@ classifier("I've been waiting for a HuggingFace course my whole life.") [{'label': 'POSITIVE', 'score': 0.9598047137260437}] ``` -We can even pass several sentences! - 複数の文章を入力することも可能です。 ```python @@ -76,39 +61,14 @@ classifier( {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -By default, this pipeline selects a particular pretrained model that has been fine-tuned for sentiment analysis in English. The model is downloaded and cached when you create the `classifier` object. If you rerun the command, the cached model will be used instead and there is no need to download the model again. - デフォルトでは、このpipelineは英語の感情分析用にファインチューニングされた特定の事前学習モデルを使用します。このモデルは `classifier` オブジェクトを作成する際にダウンロードされ、キャッシュされます。コマンドを再実行すると、キャッシュされたモデルが代わりに使用され、モデルを再度ダウンロードする必要はありません。 - -There are three main steps involved when you pass some text to a pipeline: - -1. The text is preprocessed into a format the model can understand. -2. The preprocessed inputs are passed to the model. -3. The predictions of the model are post-processed, so you can make sense of them. - - pipelineにテキストを渡す場合、主に3つのステップがあります。 1. テキストはモデルが理解できる形式に前処理される。 2. 前処理された入力がモデルに渡される。 3. 予測結果を理解できるように、モデルの後処理が行われる。 - -Some of the currently [available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html) are: - -- `feature-extraction` (get the vector representation of a text) -- `fill-mask` -- `ner` (named entity recognition) -- `question-answering` -- `sentiment-analysis` -- `summarization` -- `text-generation` -- `translation` -- `zero-shot-classification` - -Let's have a look at a few of these! - 現在[利用可能なpipeline](https://huggingface.co/transformers/main_classes/pipelines.html)の一部を紹介します。 - `feature-extraction` (テキストのベクトル表現を取得) @@ -121,11 +81,9 @@ Let's have a look at a few of these! - `translation` - `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. +## ゼロショット分類 まず、ラベル付けされていないテキストを分類する必要があるような、より困難なタスクに取り組むことから始めます。これは実際のプロジェクトでよくあるシナリオです。なぜなら、テキストにアノテーションをつけるのは通常時間がかかり、専門知識が必要だからです。このような場合、`zero-shot-classification` pipelineは非常に強力です。分類に使用するラベルを指定できるので、事前に学習したモデルのラベルに依存する必要がありません。肯定的か否定的かの2つのラベルを使って、モデルがどのようにテキストを分類するかは既に見たとおりです。しかし、他の任意のラベルセットを使ってテキストを分類することもできます。 @@ -145,21 +103,15 @@ classifier( 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} ``` -This pipeline is called _zero-shot_ because you don't need to fine-tune the model on your data to use it. It can directly return probability scores for any list of labels you want! - -このpipelineは _zero-shot_ と呼ばれる。なぜなら、これを使うために自前のデータセットでモデルのファインチューニングをする必要がないからです。任意のラベルのリストに対して直接確率スコアを返すことができます。 +このpipelineは _zero-shot_ と呼ばれます。なぜなら、これを使うために自前のデータセットでモデルのファインチューニングをする必要がないからです。任意のラベルのリストに対して直接確率スコアを返すことができます。 -✏️ **Try it out!** Play around with your own sequences and labels and see how the model behaves. - ✏️ **試してみよう!** 独自の入力とラベルで遊んでみて、モデルがどのように振る舞うか見てみましょう。 -## 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. +## 文章生成 では、pipelineを使ってテキストを生成する方法を見てみましょう。主なアイデアは、プロンプトを与えると、モデルが残りのテキストを生成してそれを補完することです。これは、多くの携帯電話に搭載されている予測入力機能に類似しています。テキスト生成にはランダム性が含まれるため、以下と同様な結果が得られないのが通常です。 @@ -177,24 +129,17 @@ generator("In this course, we will teach you how to") 'HTTP'}] ``` -You can control how many different sequences are generated with the argument `num_return_sequences` and the total length of the output text with the argument `max_length`. - 引数`num_return_sequences`で異なるシーケンスの生成数を、引数`max_length`で出力テキストの合計の長さを制御することができます。 -✏️ **Try it out!** Use the `num_return_sequences` and `max_length` arguments to generate two sentences of 15 words each. -✏️ **試してみよう!** `num_return_sequences` と `max_length` 引数を用いて、15語ずつの2つの文を生成することができます。 +✏️ **試してみよう!** `num_return_sequences` と `max_length` 引数を用いて、15語ずつの2つの文を生成してみましょう! -## Using any model from the Hub in a pipeline pipelineでHubから任意のモデルを使用する - -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). - -Let's try the [`distilgpt2`](https://huggingface.co/distilgpt2) model! Here's how to load it in the same pipeline as before: +## pipelineでHubから任意のモデルを使用する これまでの例では、タスクに応じたデフォルトのモデルを使用しましたが、特定のタスク(例えばテキスト生成)のpipelineで使用するモデルをHubから選択することも可能です。[Model Hub](https://huggingface.co/models)にアクセスし、左側の対応するタグをクリックすると、そのタスクでサポートされているモデルのみが表示されます。[このようなページ](https://huggingface.co/models?pipeline_tag=text-generation)が表示されるはずです。 @@ -218,36 +163,24 @@ generator( 'time and real'}] ``` -You can refine your search for a model by clicking on the language tags, and pick a model that will generate text in another language. The Model Hub even contains checkpoints for multilingual models that support several languages. - -Once you select a model by clicking on it, you'll see that there is a widget enabling you to try it directly online. This way you can quickly test the model's capabilities before downloading it. - 言語タグをクリックして検索するモデルを絞り込み、他の言語でテキストを生成するモデルを選ぶことができます。Model Hubには、複数の言語をサポートする多言語モデルのチェックポイントもあります。 モデルをクリックで選択すると、オンラインで直接試用できるウィジェットが表示されます。このようにして、ダウンロードする前にモデルの機能をすばやくテストすることができます。 -✏️ **Try it out!** Use the filters to find a text generation model for another language. Feel free to play with the widget and use it in a pipeline! - -✏️ **試してみよう!** フィルタ-を使って、他の言語のテキスト生成モデルを探してみましょう。ウィジェットで自由に遊んだり、pipelineで使ってみてください! +✏️ **試してみよう!** フィルターを使って、他の言語のテキスト生成モデルを探してみましょう。ウィジェットで自由に遊んだり、pipelineで使ってみてください! -### The Inference API 推論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. +### 推論API -すべてのモデルは、Hugging Face [ウェブサイト](https://huggingface.co/)で公開されているInference APIを使って、ブラウザから直接テストすることが可能です。このページでは、カスタムテキストを入力し、モデルが入力データを処理する様子を見ることで、直接モデルで遊ぶことができます。 +すべてのモデルは、Hugging Face [ウェブサイト](https://huggingface.co/)で公開されているInference APIを使って、ブラウザから直接テストすることが可能です。このページでは、任意の文字列を入力し、モデルが入力データを処理する様子を見ることで、直接モデルで遊ぶことができます。 このウィジェットを動かすInference APIは、有料製品としても提供されており、ワークフローに必要な場合は便利です。詳しくは[価格ページ](https://huggingface.co/pricing)をご覧ください。 -## 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: +## 空所穴埋め 次に試すpipelineは`fill-mask`です。このタスクのアイデアは、与えられたテキストの空白を埋めることです。 @@ -268,21 +201,15 @@ unmasker("This course will teach you all about models.", top_k=2) 'token_str': ' computational'}] ``` -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* と呼ばれます。他の空所穴埋めモデルは異なるマスクトークンを持つかもしれないので、他のモデルを探索するときには常に適切なマスクワードを確認するのが良いでしょう。それを確認する1つの方法は、ウィジェットで使用されているマスクワードを見ることです。 -✏️ **Try it out!** Search for the `bert-base-cased` model on the Hub and identify its mask word in the Inference API widget. What does this model predict for the sentence in our `pipeline` example above? - ✏️ **試してみよう!** Hub で `bert-base-cased` モデルを検索し、推論API ウィジェットでそのマスクワードを特定します。このモデルは上記の `pipeline` の例文に対して何を予測するでしょうか? -## 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: +## 固有表現認識 固有表現認識(NER)は、入力されたテキストのどの部分が人物、場所、組織などの固有表現に対応するかをモデルが見つけ出すタスクです。例を見てみましょう。 @@ -299,25 +226,17 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") ] ``` -Here the model correctly identified that Sylvain is a person (PER), Hugging Face an organization (ORG), and Brooklyn a location (LOC). - -We pass the option `grouped_entities=True` in the pipeline creation function to tell the pipeline to regroup together the parts of the sentence that correspond to the same entity: here the model correctly grouped "Hugging" and "Face" as a single organization, even though the name consists of multiple words. In fact, as we will see in the next chapter, the preprocessing even splits some words into smaller parts. For instance, `Sylvain` is split into four pieces: `S`, `##yl`, `##va`, and `##in`. In the post-processing step, the pipeline successfully regrouped those pieces. - ここでは、モデルはSylvainが人(PER)、Hugging Faceが組織(ORG)、Brooklynが場所(LOC)であることを正しく識別しています。 pipelineの作成機能でオプション `grouped_entities=True` を渡すと、同じエンティティに対応する文の部分を再グループ化するようpipelineに指示します。ここでは、名前が複数の単語で構成されていても、モデルは "Hugging" と "Face" を一つの組織として正しくグループ化しています。実際、次の章で説明するように、前処理ではいくつかの単語をより小さなパーツに分割することさえあります。例えば、`Sylvain`は4つの部分に分割されます。`S`, `##yl`, `##va`, and `##in`.です。後処理の段階で、pipelineはこれらの断片をうまく再グループ化しました。 -✏️ **Try it out!** Search the Model Hub for a model able to do part-of-speech tagging (usually abbreviated as POS) in English. What does this model predict for the sentence in the example above? - ✏️ **試してみよう!** Model Hubで英語の品詞タグ付け(通常POSと略される)を行えるモデルを検索してください。このモデルは、上の例の文に対して何を予測するでしょうか? -## Question answering 質問応答 - -The `question-answering` pipeline answers questions using information from a given context: +## 質問応答 質問応答pipelineは、与えられた文脈から得た情報を使って質問に答えます。 @@ -334,13 +253,9 @@ question_answerer( {'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} ``` -Note that this pipeline works by extracting information from the provided context; it does not generate the answer. - このpipelineは、提供されたコンテキストから情報を抽出することで動作し、答えを生成するわけではないことに注意してください。 -## 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: +## 要約 要約とは、文章中の重要な部分をすべて(あるいはほとんど)維持したまま、より短い文章にするタスクです。以下はその例です。 @@ -380,14 +295,10 @@ summarizer( 'and advance engineering .'}] ``` -Like with text generation, you can specify a `max_length` or a `min_length` for the result. - テキスト生成と同様に、結果に対して `max_length` や `min_length` を指定することができます。 -## 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: +## 翻訳 翻訳の場合、タスク名に言語ペアを指定すれば(`"translation_en_to_fr"`など)デフォルトのモデルを使うこともできますが、一番簡単なのは [Model Hub](https://huggingface.co/models) で使いたいモデルを選ぶことです。ここでは、フランス語から英語への翻訳を試してみます。 @@ -401,18 +312,12 @@ translator("Ce cours est produit par Hugging Face.") [{'translation_text': 'This course is produced by Hugging Face.'}] ``` -Like with text generation and summarization, you can specify a `max_length` or a `min_length` for the result. - テキスト生成や要約と同様に、結果に対して `max_length` や `min_length` を指定することができます。 -✏️ **Try it out!** Search for translation models in other languages and try to translate the previous sentence into a few different languages. - ✏️ **試してみよう!** 他言語の翻訳モデルを検索して、前の文章をいくつかの異なる言語に翻訳してみましょう。 -The pipelines shown so far are mostly for demonstrative purposes. They were programmed for specific tasks and cannot perform variations of them. In the next chapter, you'll learn what's inside a `pipeline()` function and how to customize its behavior. - これまで紹介したpipelineは、ほとんどがデモンストレーションのためのものです。これらは特定のタスクのためにプログラムされたものであり、それらのバリエーションを実行することはできません。次の章では、`pipeline()`関数の中身と、その動作をカスタマイズする方法を学びます。 From 5f2493a73f8d29209db97e1da26cc4dbc5b0862b Mon Sep 17 00:00:00 2001 From: blackdoor571 Date: Fri, 4 Nov 2022 20:01:26 +0900 Subject: [PATCH 430/789] modify _toctree.yml --- chapters/ja/_toctree.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index c1d0fcd7f..bc41da1f9 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -7,6 +7,10 @@ sections: - local: chapter1/1 title: イントロダクション + - local: chapter1/2 + title: 自然言語処理 / NLP(Natural Language Processing) + - local: chapter1/3 + title: Transformersで何ができる? - title: 4. モデルとトークナイザーの共有 sections: From 44f77be2fb1898e43c309b7cb8191a13353856cb Mon Sep 17 00:00:00 2001 From: Mishig Date: Tue, 8 Nov 2022 16:37:40 +0100 Subject: [PATCH 431/789] Update pr docs actions (#369) --- .github/workflows/build_pr_documentation.yml | 5 ++++- .github/workflows/delete_doc_comment.yml | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) 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 From db20ca562094391ccaa8c4a50ac6154c7e314954 Mon Sep 17 00:00:00 2001 From: David Gilbertson Date: Wed, 9 Nov 2022 22:07:27 +1100 Subject: [PATCH 432/789] Add Python syntax highlighting (#370) --- chapters/en/chapter3/3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() ``` From 36b44d102185b4d0d18f51012b57d025223f8a75 Mon Sep 17 00:00:00 2001 From: lbourdois <58078086+lbourdois@users.noreply.github.com> Date: Wed, 9 Nov 2022 13:52:14 +0100 Subject: [PATCH 433/789] [FR] Add FAQ and more (#367) Co-authored-by: Lewis Tunstall --- 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 | 2009 +++++++++++++++++---------------- chapters/fr/chapter7/5.mdx | 2186 ++++++++++++++++++------------------ chapters/fr/chapter7/6.mdx | 12 +- chapters/fr/chapter7/7.mdx | 14 +- chapters/fr/glossary/1.mdx | 63 ++ 10 files changed, 2267 insertions(+), 2121 deletions(-) create mode 100644 chapters/fr/glossary/1.mdx 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..28b04b436 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,998 +1,1011 @@ - - -# 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 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`. - - - -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. - -### 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 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'} - -```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 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`. + + + +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. + +### 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 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'} + +```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 +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} + + +### 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 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 = [] + + 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} + +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 3f317d0e9..0e5e5bd61 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1083 +1,1103 @@ - - -# 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 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 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'} - -```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 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( - books_dataset["train"].column_names -) -``` - -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)] -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 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"], - 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 assembleur 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 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 planificateur de taux d'apprentissage, nous utiliserons le planificateur 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, + ) + 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 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 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'} + +```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 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( + books_dataset["train"].column_names +) +``` + +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)] +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 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 = 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, +) +``` + +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 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, 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) +``` + +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 assembleur 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 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 planificateur de taux d'apprentissage, nous utiliserons le planificateur 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 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. From 531e3f031b6b01d9c020e254f2b13f36f02e235c Mon Sep 17 00:00:00 2001 From: Pavel <60391448+pdumin@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:48:14 +0400 Subject: [PATCH 434/789] [RU] Chapter 6 (1/2) finished (#368) --- chapters/ru/_toctree.yml | 4 ++-- chapters/ru/chapter3/2.mdx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) 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 fe380852e0b517806ecedac7ae4f0a46564ae24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilo=20Mart=C3=ADnez=20Burgos?= Date: Wed, 9 Nov 2022 11:53:16 -0500 Subject: [PATCH 435/789] Spanish translation of Chapter 5 (#366) Co-authored-by: Lewis Tunstall --- 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 ++++++++++++ 9 files changed, 2476 insertions(+) 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 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 + }, + ]} +/> From 972bcd2ee26cda2b68dab17ff2626c1f200a32af Mon Sep 17 00:00:00 2001 From: Hiroaki Funayama <47347722+hiro819@users.noreply.github.com> Date: Thu, 10 Nov 2022 22:01:21 +0900 Subject: [PATCH 436/789] Add Japanese trasnlation of chapter 1/ 7 to 10 (#359) --- chapters/ja/_toctree.yml | 8 ++ chapters/ja/chapter1/10.mdx | 262 ++++++++++++++++++++++++++++++++++++ chapters/ja/chapter1/7.mdx | 23 ++++ chapters/ja/chapter1/8.mdx | 35 +++++ chapters/ja/chapter1/9.mdx | 16 +++ 5 files changed, 344 insertions(+) create mode 100644 chapters/ja/chapter1/10.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 diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index c1d0fcd7f..3d2e0342f 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -7,6 +7,14 @@ sections: - local: chapter1/1 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/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 | 文章要約, 翻訳, 生成型質問応答  | From a44a66afa647b668490854d6a1b0c0e5f1768bc7 Mon Sep 17 00:00:00 2001 From: Cesar0106 <62568082+Cesar0106@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:27:54 -0300 Subject: [PATCH 437/789] Adding Portuguese Translation to Chapter3 (#361) --- chapters/pt/_toctree.yml | 5 +++++ chapters/pt/chapter3/1.mdx | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 chapters/pt/chapter3/1.mdx 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 From 7a365f36445abe00e3453b75a18ef30cae2f0b30 Mon Sep 17 00:00:00 2001 From: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Date: Thu, 10 Nov 2022 15:08:55 +0100 Subject: [PATCH 438/789] make style --- chapters/ja/chapter1/3.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chapters/ja/chapter1/3.mdx b/chapters/ja/chapter1/3.mdx index 73a205308..93803b215 100644 --- a/chapters/ja/chapter1/3.mdx +++ b/chapters/ja/chapter1/3.mdx @@ -40,6 +40,7 @@ Transformerのモデルがどのように機能するのかを知る前に、い ```python from transformers import pipeline + classifier = pipeline("sentiment-analysis") classifier("I've been waiting for a HuggingFace course my whole life.") ``` @@ -90,6 +91,7 @@ pipelineにテキストを渡す場合、主に3つのステップがありま ```python from transformers import pipeline + classifier = pipeline("zero-shot-classification") classifier( "This is a course about the Transformers library", @@ -117,6 +119,7 @@ classifier( ```python from transformers import pipeline + generator = pipeline("text-generation") generator("In this course, we will teach you how to") ``` @@ -147,6 +150,7 @@ generator("In this course, we will teach you how to") ```python from transformers import pipeline + generator = pipeline("text-generation", model="distilgpt2") generator( "In this course, we will teach you how to", @@ -186,6 +190,7 @@ generator( ```python from transformers import pipeline + unmasker = pipeline("fill-mask") unmasker("This course will teach you all about models.", top_k=2) ``` @@ -215,6 +220,7 @@ unmasker("This course will teach you all about models.", top_k=2) ```python from transformers import pipeline + ner = pipeline("ner", grouped_entities=True) ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") ``` @@ -242,6 +248,7 @@ pipelineの作成機能でオプション `grouped_entities=True` を渡すと ```python from transformers import pipeline + question_answerer = pipeline("question-answering") question_answerer( question="Where do I work?", @@ -261,6 +268,7 @@ question_answerer( ```python from transformers import pipeline + summarizer = pipeline("summarization") summarizer( """ @@ -304,6 +312,7 @@ summarizer( ```python from transformers import pipeline + translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") translator("Ce cours est produit par Hugging Face.") ``` From bde9ef4204d4cca8acb52f30c08d53e82ae04d07 Mon Sep 17 00:00:00 2001 From: Filippo Broggini Date: Fri, 11 Nov 2022 15:10:24 +0100 Subject: [PATCH 439/789] Typo in Chapter 2, Section 2 (#364) Replace "inputs" with "outputs". --- chapters/en/chapter2/2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From d54c30c6a99884be948b1b31c561ba4c1419044e Mon Sep 17 00:00:00 2001 From: Mishig Date: Wed, 16 Nov 2022 11:44:12 +0100 Subject: [PATCH 440/789] Revert "Update pr docs actions (#369)" This reverts commit 44f77be2fb1898e43c309b7cb8191a13353856cb. --- .github/workflows/build_pr_documentation.yml | 5 +---- .github/workflows/delete_doc_comment.yml | 7 ++----- 2 files changed, 3 insertions(+), 9 deletions(-) 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 From e04fe8b6337813493f78ff1c8a54ce20fd62cfc6 Mon Sep 17 00:00:00 2001 From: Nanachi <70123136+NanachiHub@users.noreply.github.com> Date: Fri, 18 Nov 2022 20:23:59 +1100 Subject: [PATCH 441/789] Typo (#374) --- chapters/en/chapter2/2.mdx | 4 ++-- chapters/en/chapter2/4.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index f7cd4d4e8..bfdf910f6 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -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': } ``` diff --git a/chapters/en/chapter2/4.mdx b/chapters/en/chapter2/4.mdx index 4545d2403..134488e55 100644 --- a/chapters/en/chapter2/4.mdx +++ b/chapters/en/chapter2/4.mdx @@ -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() From d00c646e1e7f1a15de967bbb836604a7de621c13 Mon Sep 17 00:00:00 2001 From: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:25:20 +0100 Subject: [PATCH 442/789] Chapter 9 - Italian (#373) --- chapters/it/_toctree.yml | 13 ++- chapters/it/chapter9/1.mdx | 37 ++++++++ chapters/it/chapter9/2.mdx | 118 +++++++++++++++++++++++ chapters/it/chapter9/3.mdx | 186 +++++++++++++++++++++++++++++++++++++ 4 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 chapters/it/chapter9/1.mdx create mode 100644 chapters/it/chapter9/2.mdx create mode 100644 chapters/it/chapter9/3.mdx 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/chapter9/1.mdx b/chapters/it/chapter9/1.mdx new file mode 100644 index 000000000..a478bea23 --- /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..8ec5ce79e --- /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..a36e67f20 --- /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 From 2a6767d2e03a2af645a9150f205237f75b607e6a Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 22 Nov 2022 10:34:01 +0100 Subject: [PATCH 443/789] Fix notebook link (#378) --- chapters/en/chapter8/4.mdx | 4 ++-- chapters/fr/chapter8/4.mdx | 4 ++-- chapters/it/chapter8/4.mdx | 4 ++-- chapters/vi/chapter8/4.mdx | 4 ++-- chapters/zh-CN/chapter8/4.mdx | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 94adcb60c..fb4f105b0 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/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/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/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/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()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 From 31658da81f108bfc038bd8010be45b391c056df1 Mon Sep 17 00:00:00 2001 From: Wonhyeong Seo Date: Tue, 22 Nov 2022 19:15:12 +0900 Subject: [PATCH 444/789] docs: feat: chapter2-1 in Korean (#375) Review by @lewtun 22/11/22 docs: fix: remove commented toc for future contributors --- README.md | 2 +- chapters/ko/_toctree.yml | 6 +++++- chapters/ko/chapter2/1.mdx | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 chapters/ko/chapter2/1.mdx 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/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에서 사용할 수 있는 모든 기능을 활용하려면 계정을 만드는 게 좋습니다. + From e328700f57499cbc2bdac72a362974a62e53211a Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 22 Nov 2022 14:51:42 +0100 Subject: [PATCH 445/789] Migrate Spaces URLs to new domain (#379) --- chapters/en/chapter7/2.mdx | 2 +- chapters/en/chapter7/3.mdx | 2 +- chapters/en/chapter7/4.mdx | 2 +- chapters/en/chapter7/5.mdx | 2 +- chapters/en/chapter7/6.mdx | 2 +- chapters/en/chapter7/7.mdx | 2 +- chapters/en/chapter9/1.mdx | 6 +- chapters/en/chapter9/2.mdx | 6 +- chapters/en/chapter9/3.mdx | 6 +- chapters/en/chapter9/4.mdx | 4 +- chapters/en/chapter9/5.mdx | 6 +- chapters/en/chapter9/6.mdx | 4 +- chapters/en/chapter9/7.mdx | 10 +- chapters/fr/chapter0/1.mdx | 220 ++-- chapters/fr/chapter7/2.mdx | 4 +- chapters/fr/chapter7/3.mdx | 4 +- chapters/fr/chapter7/4.mdx | 2022 +++++++++++++++--------------- chapters/fr/chapter7/5.mdx | 2206 ++++++++++++++++----------------- chapters/fr/chapter7/6.mdx | 4 +- chapters/fr/chapter7/7.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/chapter9/1.mdx | 6 +- chapters/it/chapter9/2.mdx | 6 +- chapters/it/chapter9/3.mdx | 6 +- 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/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/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/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 +- 64 files changed, 2904 insertions(+), 2904 deletions(-) diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 81ef920d6..23a7c153e 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -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. diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 982d8beba..059f73552 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! diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index e7b2ad3fb..12c94051e 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -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. diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 6b93c1fea..723c5cbef 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -29,7 +29,7 @@ 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. diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index b5e48fdc8..a81c66be0 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -30,7 +30,7 @@ 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. diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index ed85f59c3..11ee8a7c9 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -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). diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index 08e130eb2..fb2061904 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -20,15 +20,15 @@ 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: - + * 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..99f2abbad 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -37,7 +37,7 @@ Let's walk through the code above: 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. - + 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. @@ -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..73643bfc9 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -71,7 +71,7 @@ gr.Interface(reverse_audio, mic, "audio").launch() The code above will produce an interface like the one below (if your browser doesn't ask you for microphone permissions, open the demo in a separate tab.) - + You should now be able to record your voice and hear yourself speaking in reverse - spooky 👻! @@ -118,7 +118,7 @@ gr.Interface( ).launch() ``` - + ### The `launch()` method @@ -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..0d11f7680 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -50,7 +50,7 @@ gr.Interface( Using the options above, we end up with a more complete interface. Try the interface below: - + ### 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. @@ -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..2b1587332 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -41,7 +41,7 @@ gr.Interface.load( The code above will produce the interface below: - + 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 @@ -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..2ceb35237 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -53,7 +53,7 @@ iface = gr.Interface( 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, @@ -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..10654a24c 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -56,7 +56,7 @@ with demo: demo.launch() ``` - + This simple example above introduces 4 concepts that underlie Blocks: @@ -121,7 +121,7 @@ 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. @@ -160,7 +160,7 @@ with gr.Blocks() as demo: demo.launch() ``` - + ### Creating multi-step demos @@ -200,7 +200,7 @@ with demo: demo.launch() ``` - + ### Updating Component Properties @@ -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/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 28b04b436..236194972 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,1011 +1,1011 @@ - - -# 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 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`. - - - -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. - -### 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 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'} - -```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 -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} - - -### 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 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 = [] - - 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} - -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 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`. + + + +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. + +### 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 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'} + +```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 +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} + + +### 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 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 = [] + + 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} + +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 0e5e5bd61..cb24bcd28 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1103 +1,1103 @@ - - -# 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, - ) - 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 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 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'} - -```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 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( - books_dataset["train"].column_names -) -``` - -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)] -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 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 = 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, -) -``` - -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 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, 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) -``` - -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 assembleur 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 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 planificateur de taux d'apprentissage, nous utiliserons le planificateur 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, + ) + 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 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 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'} + +```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 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( + books_dataset["train"].column_names +) +``` + +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)] +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 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 = 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, +) +``` + +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 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, 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) +``` + +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 assembleur 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 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 planificateur de taux d'apprentissage, nous utiliserons le planificateur 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 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/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/chapter9/1.mdx b/chapters/it/chapter9/1.mdx index a478bea23..b1b73ae4b 100644 --- a/chapters/it/chapter9/1.mdx +++ b/chapters/it/chapter9/1.mdx @@ -20,15 +20,15 @@ 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. diff --git a/chapters/it/chapter9/2.mdx b/chapters/it/chapter9/2.mdx index 8ec5ce79e..8907fc06c 100644 --- a/chapters/it/chapter9/2.mdx +++ b/chapters/it/chapter9/2.mdx @@ -37,7 +37,7 @@ Analizziamo il codice qui sopra: 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! @@ -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() ``` - + 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. @@ -113,6 +113,6 @@ 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 index a36e67f20..9f4dbc7a4 100644 --- a/chapters/it/chapter9/3.mdx +++ b/chapters/it/chapter9/3.mdx @@ -71,7 +71,7 @@ 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 👻! @@ -118,7 +118,7 @@ gr.Interface( ).launch() ``` - + ### Il metodo `launch()` @@ -176,7 +176,7 @@ gr.Interface( 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 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/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/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/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 92c615d08205b849ee5a38d1f618742e1c17ee7c Mon Sep 17 00:00:00 2001 From: Wonhyeong Seo Date: Tue, 22 Nov 2022 23:37:49 +0900 Subject: [PATCH 446/789] docs: feat: same links across languages (#380) Added custom anchors using double square brackets, e.g. [[formatted-anchor]] --- 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 | 12 ++++++------ chapters/en/chapter2/3.mdx | 12 ++++++------ chapters/en/chapter2/4.mdx | 20 ++++++++++---------- 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 | 32 ++++++++++++++++---------------- chapters/en/chapter7/3.mdx | 16 ++++++++-------- chapters/en/chapter7/4.mdx | 26 +++++++++++++------------- chapters/en/chapter7/5.mdx | 24 ++++++++++++------------ chapters/en/chapter7/6.mdx | 12 ++++++------ chapters/en/chapter7/7.mdx | 26 +++++++++++++------------- 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 | 26 +++++++++++++------------- 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 | 2 +- chapters/en/chapter9/2.mdx | 4 ++-- chapters/en/chapter9/3.mdx | 12 ++++++------ chapters/en/chapter9/4.mdx | 10 +++++----- chapters/en/chapter9/5.mdx | 6 +++--- chapters/en/chapter9/6.mdx | 6 +++--- chapters/en/chapter9/7.mdx | 14 +++++++------- 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 +- 80 files changed, 341 insertions(+), 342 deletions(-) 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: @@ -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 134488e55..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]] @@ -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'} @@ -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 059f73552..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'} @@ -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 12c94051e..bc957c37b 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -1,6 +1,6 @@ -# Translation +# Translation[[translation]] {#if fw === 'pt'} @@ -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 723c5cbef..b8afcfaa0 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,6 +1,6 @@ -# Summarization +# Summarization[[summarization]] {#if fw === 'pt'} @@ -33,7 +33,7 @@ Although there already exist various fine-tuned models for summarization on the 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]] @@ -313,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]] @@ -392,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: @@ -454,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: @@ -466,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: @@ -789,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: @@ -913,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: @@ -1016,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 a81c66be0..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'} @@ -34,7 +34,7 @@ In [Chapter 6](/course/chapter6) we created an efficient tokenizer to process Py 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 11ee8a7c9..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'} @@ -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]] @@ -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]] -### 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. diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index 0d11f7680..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 @@ -52,7 +52,7 @@ Using the options above, we end up with a more complete interface. Try the inter -### 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. diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index 2b1587332..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]]