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/116] 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/116] 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/116] 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/116] 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/116] 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/116] 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 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 007/116] 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 cfee90e23fe4264ec9288ec912aa739464794674 Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Tue, 3 May 2022 13:33:49 +0800 Subject: [PATCH 008/116] 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 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 009/116] 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 010/116] 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 011/116] 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 012/116] 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 15fc23267eaa239fe83dba82ce425377ceb67977 Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Thu, 12 May 2022 08:57:25 +0200 Subject: [PATCH 013/116] 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 a88b0bba32ce7f37b94da8a4c2ca67b424eabffb Mon Sep 17 00:00:00 2001 From: Avishek Das Date: Mon, 16 May 2022 21:10:52 +0600 Subject: [PATCH 014/116] 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 015/116] [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 016/116] [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 017/116] 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 018/116] 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 019/116] [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 020/116] 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 021/116] =?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 022/116] [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 d19424aeb4f69c2acedf74f34bd8061185c7812a Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 18 May 2022 12:18:28 +0200 Subject: [PATCH 023/116] 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 024/116] 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 025/116] 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 af0bfc86368e317a88f19f3547d02d3b72582371 Mon Sep 17 00:00:00 2001 From: Fermin Ordaz Date: Thu, 19 May 2022 02:47:41 -0700 Subject: [PATCH 026/116] 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 027/116] 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 028/116] [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 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 029/116] [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 030/116] 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 1302cefb911ea97e191f8495c442e9b7fcaba8d2 Mon Sep 17 00:00:00 2001 From: Bahram Shamshiri Date: Fri, 20 May 2022 14:39:16 +0430 Subject: [PATCH 031/116] [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 032/116] 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 42d203089919dad32385f76500914336a8e35ee7 Mon Sep 17 00:00:00 2001 From: Batuhan Ayhan Date: Mon, 23 May 2022 21:50:21 +0300 Subject: [PATCH 033/116] [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 034/116] [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 035/116] [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 e55809abd73beeb36fc8768fb842281bc3ee9a1f Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 24 May 2022 18:30:04 +0200 Subject: [PATCH 036/116] 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 037/116] [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 038/116] [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 039/116] 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 040/116] 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 041/116] 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 042/116] 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 043/116] 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 044/116] 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 045/116] 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 046/116] 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 047/116] 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 048/116] 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 049/116] 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 050/116] [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 051/116] 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 052/116] [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 053/116] 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 054/116] 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 055/116] 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 056/116] 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 057/116] 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 058/116] 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 059/116] 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 060/116] 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 061/116] 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 062/116] 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 063/116] 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 064/116] 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 065/116] 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 066/116] 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 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 067/116] 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 068/116] [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 069/116] 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 070/116] 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 071/116] [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 072/116] 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 85df4d93617b6005ff64bc272ae6e2f1e0003440 Mon Sep 17 00:00:00 2001 From: Vedant Pandya Date: Tue, 14 Jun 2022 20:53:03 +0530 Subject: [PATCH 073/116] 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 e216c2f7c3f4c43d3fd4182ea72c2214f86f1fe0 Mon Sep 17 00:00:00 2001 From: Fermin Ordaz Date: Thu, 16 Jun 2022 23:20:46 -0700 Subject: [PATCH 074/116] 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 075/116] [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 076/116] [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 077/116] [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 078/116] 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 079/116] 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 44690a3e2a54d9ceedb42ee4f8230b7b8ae68b41 Mon Sep 17 00:00:00 2001 From: Hiromu Hota Date: Sun, 19 Jun 2022 13:03:05 -0700 Subject: [PATCH 080/116] 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 081/116] 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 082/116] [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 083/116] 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 084/116] 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 085/116] 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 086/116] 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 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 087/116] 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 088/116] [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 089/116] [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 090/116] 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 091/116] 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 092/116] 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 093/116] 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 094/116] [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 095/116] 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 096/116] 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 097/116] 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 098/116] [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 099/116] [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 100/116] 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 101/116] 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 102/116] 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 103/116] 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 104/116] 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 105/116] 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 106/116] 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 107/116] 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 108/116] 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 109/116] 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 110/116] 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 111/116] 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 112/116] 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 113/116] 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 114/116] 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 115/116] 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 116/116] 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