From 14e8f4e1d8d529d4000afd71a858f38f18e839ab Mon Sep 17 00:00:00 2001 From: 1375626371 <1375626371@qq.com> Date: Wed, 20 Nov 2024 03:39:37 +0800 Subject: [PATCH] finish review ZH-CN chapter1-6 --- chapters/zh-CN/chapter0/1.mdx | 50 +++---- chapters/zh-CN/chapter1/1.mdx | 61 +++++---- chapters/zh-CN/chapter1/10.mdx | 86 ++++++------ chapters/zh-CN/chapter1/2.mdx | 20 +-- chapters/zh-CN/chapter1/3.mdx | 158 +++++++++++++--------- chapters/zh-CN/chapter1/4.mdx | 102 +++++++------- chapters/zh-CN/chapter1/5.mdx | 16 +-- chapters/zh-CN/chapter1/6.mdx | 13 +- chapters/zh-CN/chapter1/7.mdx | 14 +- chapters/zh-CN/chapter1/8.mdx | 8 +- chapters/zh-CN/chapter1/9.mdx | 10 +- chapters/zh-CN/chapter2/1.mdx | 19 +-- chapters/zh-CN/chapter2/2.mdx | 149 +++++++++++---------- chapters/zh-CN/chapter2/3.mdx | 117 +++++++--------- chapters/zh-CN/chapter2/4.mdx | 109 +++++++-------- chapters/zh-CN/chapter2/5.mdx | 83 +++++------- chapters/zh-CN/chapter2/6.mdx | 47 ++++--- chapters/zh-CN/chapter2/7.mdx | 28 ++-- chapters/zh-CN/chapter2/8.mdx | 154 ++++++++++----------- chapters/zh-CN/chapter3/1.mdx | 14 +- chapters/zh-CN/chapter3/2.mdx | 139 ++++++++++--------- chapters/zh-CN/chapter3/3.mdx | 48 +++---- chapters/zh-CN/chapter3/3_tf.mdx | 49 +++---- chapters/zh-CN/chapter3/4.mdx | 71 +++++----- chapters/zh-CN/chapter3/5.mdx | 18 +-- chapters/zh-CN/chapter3/6.mdx | 171 ++++++++++++------------ chapters/zh-CN/chapter4/1.mdx | 14 +- chapters/zh-CN/chapter4/2.mdx | 16 +-- chapters/zh-CN/chapter4/3.mdx | 222 +++++++++++++++---------------- chapters/zh-CN/chapter4/4.mdx | 50 +++---- chapters/zh-CN/chapter4/5.mdx | 8 +- chapters/zh-CN/chapter4/6.mdx | 124 ++++++++--------- chapters/zh-CN/chapter5/1.mdx | 21 ++- chapters/zh-CN/chapter5/2.mdx | 55 ++++---- chapters/zh-CN/chapter5/3.mdx | 218 +++++++++++++++--------------- chapters/zh-CN/chapter5/4.mdx | 89 ++++++------- chapters/zh-CN/chapter5/5.mdx | 112 ++++++++-------- chapters/zh-CN/chapter5/6.mdx | 75 ++++++----- chapters/zh-CN/chapter5/7.mdx | 16 +-- chapters/zh-CN/chapter5/8.mdx | 124 ++++++++--------- chapters/zh-CN/chapter6/1.mdx | 16 +-- chapters/zh-CN/chapter6/10.mdx | 163 ++++++++++++----------- chapters/zh-CN/chapter6/2.mdx | 86 ++++++------ chapters/zh-CN/chapter6/3.mdx | 114 ++++++++-------- chapters/zh-CN/chapter6/3b.mdx | 121 +++++++++-------- chapters/zh-CN/chapter6/4.mdx | 59 ++++---- chapters/zh-CN/chapter6/5.mdx | 108 +++++++-------- chapters/zh-CN/chapter6/6.mdx | 106 +++++++-------- chapters/zh-CN/chapter6/7.mdx | 131 +++++++++--------- chapters/zh-CN/chapter6/8.mdx | 176 ++++++++++++------------ chapters/zh-CN/chapter6/9.mdx | 14 +- 51 files changed, 2002 insertions(+), 1990 deletions(-) diff --git a/chapters/zh-CN/chapter0/1.mdx b/chapters/zh-CN/chapter0/1.mdx index 8bac2386f..4861a6d1f 100644 --- a/chapters/zh-CN/chapter0/1.mdx +++ b/chapters/zh-CN/chapter0/1.mdx @@ -1,34 +1,34 @@ # 课程简介 [[课程简介]] -欢迎来到Hugging Face课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 +欢迎来到 Hugging Face 课程!本小节将指导你搭建工作环境。如果你刚开始学习本课程,我们建议你先阅读 [第一章](/course/chapter1) ,然后返回此处搭建环境,以便你亲自尝试和实现代码。 -我们将在本课程中使用的所有库都以 Python 包的形式提供,因此在这里我们将向您展示如何设置 Python 环境并安装您需要的特定库。 +本课程涉及的所有库均以 Python 包形式提供,因此在这里我们将展示如何搭建 Python 环境并安装所需要的库。 -我们将介绍两种设置工作环境的方法,使用 Colab 笔记本或 Python 虚拟环境。选择任意一种趁手的方式即可。对于初学者,我们强烈建议您从使用 Colab 笔记本开始(国内无法访问,可以跳过,直接查阅本页**安装依赖**那一节即可在本地的环境下运行,译者注)。 +我们将介绍两种设置工作环境的方法,使用 Colab Notebook 或 Python 虚拟环境。选择任意一种趁手的方式即可。对于初学者,我们强烈建议你从使用 Colab Notebook 开始(国内无法访问,可以跳过,直接查阅本页 **安装依赖** 那一节即可在本地的环境下运行,译者注)。 -请注意,Python 虚拟环境的一些命令不支持Windows系统。如果您在 Windows 上运行,我们建议您继续使用 Colab 笔记本。如果您使用的是 Linux 发行版或 macOS,则可以使用此处的任一方法。 +请注意,Python 虚拟环境的一些命令不支持 Windows 系统。如果你使用的操作系统是 Windows,我们建议你继续使用 Colab Notebook 如果你使用的操作系统是 Linux 发行版或 macOS,则可以使用此处的任一方法。 -大多数课程和服务都依赖于您拥有 Hugging Face 帐户。我们建议现在创建一个:[创建一个账号](https://huggingface.co/join). +大多数课程和服务都依赖于你拥有 Hugging Face 帐户。我们建议现在创建一个: [创建一个账号](https://huggingface.co/join) 。 -## 使用 Google Colab 笔记本 [[使用 Google Colab 笔记本]] +## 使用 Google Colab Notebook [[使用 Google Colab Notebook ]] -使用 Colab notebook 是最简单的设置;可以在浏览器中启动Notebook并直接开始编写自己的代码! +使用 Colab Notebook 是最简单的搭建方式;可以在浏览器中启动 Notebook 并直接开始编写自己的代码! -如果您不熟悉 Colab,我们建议您从[这个介绍](https://colab.research.google.com/notebooks/intro.ipynb)开始。Colab 提供一些加速硬件,例如 GPU 或 TPU,并且当我们使用的算力比较少的时候是免费的。 +如果你不熟悉 Colab,建议你先查看 [Colab 使用入门](https://colab.research.google.com/notebooks/intro.ipynb) 。Colab 提供一些加速硬件,例如 GPU 或 TPU,并且当我们使用的算力比较少的时候是免费的。 -当打开 Colab 后,创建一个新笔记本: +当打开 Colab 后,创建一个新的 Notebook
An empty colab notebook
-下一步是安装我们将在本课程中使用的库。我们将使用 **pip** 进行安装,它是 Python 的包管理器。在Notebook中,您可以通过加上!字符表示执行系统命令,所以安装🤗 Transformers 的命令如下: +下一步是安装我们将在本课程中使用的库。我们将使用 **pip** 进行安装,它是 Python 的包管理器。在 Notebook 中,你可以通过加上!字符表示执行系统命令,所以安装🤗 Transformers 的命令如下: ``` !pip install transformers ``` -您可以通过在运行 Python 时导入包来判断是否正确安装了该包: +你可以通过在运行 Python 时导入包来判断是否正确安装了该包: ``` import transformers @@ -38,25 +38,25 @@ import transformers A gif showing the result of the two commands above: installation and import -这将安装一个非常轻量级的 🤗 Transformers。并没有安装机器学习框架(如 PyTorch 或 TensorFlow)。由于之后我们将使用该库的许多不同功能,我们建议安装开发版本,它带有几乎所有所需的依赖项: +这将安装一个没有安装机器学习框架(如 PyTorch 或 TensorFlow)非常轻量级的 🤗 Transformers。由于之后我们将使用该库的许多不同功能,我们建议安装开发版本,它带有几乎所有所需的依赖项: ``` !pip install transformers[sentencepiece] ``` -这将需要一些时间,但当完成之后您就做好学习剩下的课程的环境准备了! +这将需要一些时间,但当完成之后你就做好学习剩下的课程环境的全部准备了! ## 使用 Python 虚拟环境 [[使用 Python 虚拟环境]] -如果您更喜欢使用 Python 虚拟环境,那么第一步是在您的系统上安装 Python。我们建议您按照[这个教程](https://realpython.com/installing-python/)进行配置。 +如果你更喜欢使用 Python 虚拟环境,那么第一步是在你的系统上安装 Python。我们建议你按照 [该指南](https://realpython.com/installing-python/) 进行配置。 -安装 Python 后,您应该能够在终端中运行 Python 命令。您可以先运行以下命令来检验爱装是否正确,然后再继续下一步:`python --version`。这应该会打印出您系统上现在可用的 Python 版本。 +安装 Python 后,你应该能够在终端中运行 Python 命令。你可以先运行此命令: `python --version` 来检验安装是否正确,然后再继续下一步。这应该会打印出你系统上现在可用的 Python 版本。 -在终端中运行 Python 命令(例如 `python --version`)时,您应该将运行命令的这个Python视为系统上的“默认”Python。我们建议保持这个默认的Python安装程序没有任何包,当运行某个程序的时候就为那个程序创建一个单独的运行环境 - 这样,每个应用程序都可以有自己的依赖项和包,您无需担心与其他应用程序潜在的兼容性问题。 +在终端中运行 Python 命令(例如 `python --version` )时,运行命令的这个 Python 可以视为系统上的“默认”Python。我们建议保持这个默认的 Python 安装程序没有任何包,当运行某个程序的时候就为那个程序创建一个单独的运行环境 —— 这样,每个应用程序都可以有自己的依赖项和包,你无需担心与其他应用程序潜在的兼容性问题。 -在 Python 中,这是通过 [*虚拟环境*](https://docs.python.org/3/tutorial/venv.html)实现的,虚拟环境会创建许多目录树,每个目录树都包含具有特定 Python 版本的 Python 安装以及应用程序所需的所有包。可以使用许多不同的工具来创建这样的虚拟环境,但在此我们将使用官方 Python 包:[`venv`](https://docs.python.org/3/library/venv.html#module-venv). +在 Python 中,这是通过 [*虚拟环境*](https://docs.python.org/3/tutorial/venv.html) 实现的,虚拟环境会创建许多目录树,每个目录树都包含具有特定 Python 版本的 Python 安装以及应用程序所需的所有包。可以使用许多不同的工具来创建这样的虚拟环境,但在此我们将使用官方 Python 包: [`venv`](https://docs.python.org/3/library/venv.html#module-venv) 。 -首先,创建您希望Transformers所在的目录 - 例如,您可能希望在主目录的根目录下创建一个名为 *Transformers-course* 的新目录: +首先,创建你希望 Transformers 所在的目录 - 例如,你可能希望在主目录的根目录下创建一个名为 `Transformers-course` 的新目录: ``` mkdir ~/transformers-course @@ -69,7 +69,7 @@ cd ~/transformers-course python -m venv .env ``` -您现在应该在原本为空的文件夹中看到一个名为 *.env* 的目录: +你现在应该在原本为空的文件夹中看到一个名为 `.env` 的目录: ``` ls -a @@ -79,17 +79,17 @@ ls -a . .. .env ``` -您可以使用`activate`和`deactivate`命令来控制进入和退出您的虚拟环境: +你可以使用 `activate` 和 `deactivate` 命令来控制进入和退出你的虚拟环境: ``` -# Activate the virtual environment +# 激活虚拟环境 source .env/bin/activate -# Deactivate the virtual environment +# 退出虚拟环境 deactivate ``` -您可以通过运行 `which python` 命令来检测虚拟环境是否被激活:如果它指向虚拟环境,那么您已经成功激活了它! +你可以通过运行 `which python` 命令来检测虚拟环境是否被激活:如果它指向虚拟环境,那么你已经成功激活了它! ``` which python @@ -101,10 +101,10 @@ which python ### 安装依赖 [[安装依赖]] -与使用 Google Colab 实例的上一节一样,您现在需要安装继续所需的包。 同样,您可以使用 `pip` 包管理器安装 🤗 Transformers 的开发版本: +与前面使用 Google Colab 的部分一样,你现在需要安装继续学习所需的软件包。同样,你可以使用 `pip` 包管理器安装 🤗 Transformers 的开发版本: ``` pip install "transformers[sentencepiece]" ``` -您现在已准备就绪,可以开始了! \ No newline at end of file +你现在已准备就绪,可以开始了! \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 1300aa63b..e509d628f 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -5,12 +5,11 @@ classNames="absolute z-10 right-0 top-0" /> -## 欢迎来到🤗课程 [[欢迎来到🤗课程]] +## 欢迎来到🤗 Hugging Face 课程 [[欢迎来到🤗 Hugging Face 课程]] -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - +本课程将教你如何使用 Hugging Face 生态系统的库进行自然语言处理(NLP)。这些库包括 🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate,以及 Hugging Face Hub。本课程完全免费且无广告。 ## 有什么是值得期待的?[[有什么是值得期待的?]] @@ -21,74 +20,74 @@ -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,你将了解 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享你的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets 和 🤗 Tokenizers 的基础知识。在本部分结束时,你将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章将超越 NLP,探讨如何使用 Transformer 模型解决语音处理和计算机视觉任务。在此过程中,你将学习如何构建和分享模型的演示,并将它们优化为生产环境。完成这部分课程后,你将准备好将 🤗 Transformers 应用于(几乎)任何机器学习问题! 这个课程: * 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 +* 在完成入门深度学习课程后效果更佳,例如 [DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对学习有所帮助 -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! +完成本课程后,我们建议你查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) ,该课程涵盖了诸如朴素贝叶斯和 LSTM 等传统 NLP 模型的广泛内容,非常值得了解! ## 我们是谁?[[我们是谁?]] 关于作者: -[**Abubakar Abid**](https://huggingface.co/abidlabs) 在斯坦福大学获得应用机器学习博士学位。 在攻读博士学位期间,他创立了 [Gradio](https://github.com/gradio-app/gradio),这是一个开源 Python 库,已用于构建超过 600,000 个机器学习演示。 Gradio 被 Hugging Face 收购,Abubakar 现在是该公司的机器学习团队负责人。 + [**Abubakar Abid**](https://huggingface.co/abidlabs) 在斯坦福大学获得应用机器学习博士学位。在攻读博士学位期间,他创立了 [Gradio](https://github.com/gradio-app/gradio) ,这是一个开源 Python 库,已用于构建超过 600,000 个机器学习演示。Gradio 被 Hugging Face 收购,Abubakar 现在在 Hugging Face 担任机器学习团队负责人。 -[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + [**Matthew Carrigan**](https://huggingface.co/Rocketknight1) 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在 Trinity College Dublin 担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但仍然对机器人永生抱有很高的期望。 -[**Lysandre Debut**](https://huggingface.co/lysandre) 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + [**Lysandre Debut**](https://huggingface.co/lysandre) 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让 NLP 对每个人都变得易用。 -[**Sylvain Gugger**](https://huggingface.co/sgugger) 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + [**Sylvain Gugger**](https://huggingface.co/sgugger) 是 Hugging Face 的一名研究工程师,也是 🤗Transformers 库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与 Jeremy Howard 共同编写了 [Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) 。他的研究重点是通过设计和改进技术,使模型在有限资源上进行快速训练,使深度学习更容易普及。 -[**Dawood Khan**](https://huggingface.co/dawoodkhan82) 是 Hugging Face 的机器学习工程师。 他来自纽约,毕业于纽约大学计算机科学专业。 在担任 iOS 工程师几年后,Dawood 辞职并与其他联合创始人一起创办了 Gradio。 Gradio 最终被 Hugging Face 收购。 + [**Dawood Khan**](https://huggingface.co/dawoodkhan82) 是 Hugging Face 的机器学习工程师。他来自纽约,毕业于纽约大学计算机科学专业。在担任 iOS 工程师几年后,Dawood 辞职并与其他联合创始人一起创办了 Gradio。Gradio 最终被 Hugging Face 收购。 -[**Merve Noyan**](https://huggingface.co/sgugger) 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + [**Merve Noyan**](https://huggingface.co/sgugger) 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人都可以使用机器学习。 -[**Lucile Saulnier**](https://huggingface.co/SaulLu) 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + [**Lucile Saulnier**](https://huggingface.co/SaulLu) 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 -[**Lewis Tunstall**](https://huggingface.co/lewtun) 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 + [**Lewis Tunstall**](https://huggingface.co/lewtun) 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) 的作者之一。 -[**Leandro von Werra**](https://huggingface.co/lvwerra) 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + [**Leandro von Werra**](https://huggingface.co/lvwerra) 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) 的作者之一。他拥有多年的行业经验,并在整个机器学习技术栈上进行了工作。 ## FAQ[[faq]] -这里有一些经常被提到的问题: +这里有一些经常被提到的问题: - **参加本课程是否会获得认证?** -目前,我们没有获得此课程的任何认证。 但是,我们正在为 Hugging Face 生态系统制定认证计划——敬请期待! +目前我们还没有为这门课程提供认证。然而,我们正在为 Hugging Face 生态系统制定认证计划——敬请期待! - **我应该在这门课程上花多少时间?** -本课程的每一章都设计为在 1 周内完成,每周大约需要 6-8 小时的学习时间。 但是,您可以花尽可能多的时间来完成课程。 +本课程的每一章都设计为在 1 周内完成,每周大约需要 6-8 小时的学习时间。但是,但你可以根据自己的需要随意安排学习时间。 - **如果我有问题,我可以在哪里提问?** -如果您对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动重定向到 [Hugging Face 论坛](https:// discuss.huggingface.co/): +如果你对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动跳转到 [Hugging Face 论坛](https:// discuss.huggingface.co/) : Hugging Face论坛链接 -请注意,如果您想在完成课程后进行更多练习,论坛上还提供了[项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表。 +请注意,如果你想在完成课程后进行更多练习,论坛上还提供了 [项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表,如果你希望在完成课程后进行更多实践,可以参考这些想法。 - **我在哪里可以获得课程的代码?** 对于每个部分,单击页面顶部的横幅可以在 Google Colab 或 Amazon SageMaker Studio Lab 中运行代码: -Hugging Face课程笔记本链接 +Hugging Face课程Notebook 链接 -包含课程所有代码的 Jupyter 笔记本托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。 如果您希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 +包含课程所有代码的 Jupyter Notebook 托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。如果你希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 - **我如何为课程做出贡献?** -有很多方法可以为课程做出贡献! 如果您发现拼写错误或错误,请在 [`course`](https://github.com/huggingface/course) 仓库中提出问题。 如果您想帮助将课程翻译成您的母语,请在[此处](https://github.com/huggingface/course#translating-the-course-into-your-language) 查看说明。 +有很多方法可以为课程做出贡献!如果你发现拼写错误或错误,请在 [`course`](https://github.com/huggingface/course) 仓库中提出问题。如果你想帮助将课程翻译成你的母语,请在 [此处](https://github.com/huggingface/course#translating-the-course-into-your-language) 查看说明。 -- ** 每个翻译的选择是什么?** -每个翻译都有一个词汇表和“TRANSLATING.txt”文件,其中详细说明了为机器学习术语等所做的选择。您可以在 [此处](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt)。 +- **翻译的时候有没有术语表** +每个翻译都有一个词汇表和“TRANSLATING.txt”文件,其中详细说明了为机器学习术语等所做的选择。你可以在 [此处](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt) 找到德语的示例。 - **我可以使用这门课程再次进行创作吗?** -当然! 该课程是根据宽松的 [Apache 2 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布的。 这意味着您必须按照诚信的原则,提供许可证的链接,并指出是否进行了更改。您可以以任何合理的方式这样做,但不能以任何表明许可方认可您或您的使用的方式。 如果您想引用该课程,请使用以下 BibTeX: +当然!该课程是根据宽松的 [Apache 2 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布的。这意味着你必须按照诚信的原则,提供许可证的链接,并指出是否进行了更改。你可以以任何合理的方式这样做,但不能以任何表明许可方认可你或你的使用的方式。如果你想引用该课程,请使用以下 BibTeX: ``` @misc{huggingfacecourse, @@ -102,7 +101,7 @@ ## 让我们开始吧![[让我们开始吧!]] -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +你准备好了吗?在本章中,你将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等 NLP 任务 * 关于 Transformer 架构 * 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx index 84a5cb639..2900c46d2 100644 --- a/chapters/zh-CN/chapter1/10.mdx +++ b/chapters/zh-CN/chapter1/10.mdx @@ -2,10 +2,10 @@ # 章末小测试 [[章末小测试]] - +/>` 这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 @@ -13,26 +13,25 @@ ### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? - roberta-large-mnli 页面回顾一下." + explain: "点击前往`roberta-large-mnli` 页面 再仔细看一下。" }, { text: "文本分类", - explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", + explain: "更准确地说,它能通过三个标签(矛盾,无关,蕴含)分类,判断两句话是否在逻辑上有关联——这项任务也称为自然语言推理(natural language inference)。", correct: true }, { text: "文本生成", - explain: "点击前往roberta-large-mnli 页面回顾一下." + explain: "点击前往`roberta-large-mnli` 页面 ``再仔细看一下。" } ]} /> -### 2. 下面的代码将会返回什么结果? +### 2. 下面的代码将会输出什么结果? ```py from transformers import pipeline @@ -44,16 +43,16 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") sentiment-analysis pipeline将会返回这些." + text: "它将输出带有 \"positive\" 或者 \"negative\"标签分类的分数。", + explain: "不对 —— sentiment-analysis(情感分析) pipeline才会输出这些。" }, { - text: "它将返回一个生成的文本来完成这句话。", - explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", + text: "它将生成这句话的下一句话。", + explain: "不对 —— text-generation(文本生成) pipeline才会输出这些。", }, { - text: "它将返回代表人员、组织或位置的单词。", - explain: "此外,使用 grouped_entities=True,它会将属于同一实体的单词组合在一起,例如“Hugging Face”。", + text: "它找出代表人员、组织或位置的单词。", + explain: "正解! 此外,使用 grouped_entities=True,可以将属于同一实体的单词组合在一起,例如“Hugging Face”。", correct: true } ]} @@ -71,17 +70,17 @@ result = filler("...") has been waiting for you.", - explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" + text: "This <mask> has been waiting for you.", + explain: "不对。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" }, { text: "This [MASK] has been waiting for you.", - explain: "正解! 这个模型的mask的掩码是[MASK].", + explain: "正解! 这个模型的mask token是[MASK]。", correct: true }, { text: "This man has been waiting for you.", - explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" + explain: "不对。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入中有一个mask token。" } ]} /> @@ -98,21 +97,21 @@ result = classifier("This is a course about the Transformers library") candidate_labels=[...].", + text: "这个pipeline需要提供用来分类此文本的标签。", + explain: "正解 —— 正确的代码需要包含:candidate_labels=[...]。", correct: true }, { text: "这个pipeline需要多个句子,而不仅仅是一个。", - explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" + explain: "不对。尽管正确使用时,此pipeline可以同时处理多个句子(所有其他pipeline也是一样)。" }, { - text: "像往常一样,🤗 Transformers库出故障了。", + text: "🤗 Transformers库又出故障了。", explain: "对此,我们不予置评!" }, { text: "该pipeline需要更长的输入; 这个句子太短了。", - explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" + explain: "不对。 不过请注意,在这个pipeline处理太长的文本时会将其截断。" } ]} /> @@ -122,12 +121,12 @@ result = classifier("This is a course about the Transformers library") 自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", + explain: "预训练通常是`自监督(self-supervised)`,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", correct: true }, { @@ -154,17 +152,17 @@ result = classifier("This is a course about the Transformers library") ]} /> - ### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 + - ### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? + @@ -199,15 +197,15 @@ result = classifier("This is a course about the Transformers library") -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 +在深入了解 Transformer 模型之前,让我们快速回顾一下自然语言处理是什么以及它为什么如此重要。 -## 什么是自然语言处理? [[什么是自然语言处理?]] +## 什么是自然语言处理?[[什么是自然语言处理?]] -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 以下是常见 NLP 任务的列表,每个任务都有一些示例: -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 +- **对整个句子进行分类**:获取评论的情感,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关。 +- **对句子中的每个词进行分类**:识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织)。 +- **生成文本内容**:用自动生成的文本完成提示,用掩码词填充文本中的空白。 +- **从文本中提取答案**:给定问题和上下文,根据上下文中提供的信息提取问题的答案。 +- **从输入文本生成新句子**:将文本翻译成另一种语言,对文本进行总结。 -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像的描述。 ## 为什么具有挑战性?[[为什么具有挑战性?]] -计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file +计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言的复杂性,我们很难直接设计算法来处理此类问题。已经有很多关于如何表示文本的研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 119f15600..7563b179e 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Transformers能做什么? [[Transformers能做什么?]] +# Transformers 能做什么?[[Transformers 能做什么?]] -在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 + +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具: `pipeline()` 函数。 + -👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 -如果您想在本地运行示例,我们建议您查看准备. +👀 看到那个右上角的在 Colab 中打开(Open in Colab)的按钮了吗?单击它就可以打开一个包含本节所有代码示例的 Google Colab Notebook 每一个有实例代码的小节都会有它。 + +如果你想在本地运行示例,我们建议你查看第 0 章。 + -## Transformer被应用于各个方面! [[Transformer被应用于各个方面!]] +## Transformers 无处不在![[Transformer 被应用于各个方面!]] + Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: 使用 Hugging Face 的公司 -[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! +[🤗 Transformers 库](https://github.com/huggingface/transformers) 提供了创建和使用这些共享模型的功能。 [Hugging Face 模型中心(以下简称“Hub”)](https://huggingface.co/models) 包含数千个任何人都可以下载和使用的预训练模型。你还可以将自己的模型上传到 Hub! -⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! + +⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 [Huggingface.co 帐户](https://huggingface.co/join) 即可使用所有可用功能! + 在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 -## 使用pipelines [[使用pipelines]] - - -(这里有一个视频,但是国内可能打不开,译者注) +## 使用 pipelines [[使用 pipelines]] + -🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: +🤗 Transformers 库中最基本的对象是 `pipeline()` 函数。它将模型与所需的预处理和后续处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的结果: ```python from transformers import pipeline @@ -43,43 +48,44 @@ classifier("I've been waiting for a HuggingFace course my whole life.") ``` ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` +``` 我们也可以多传几句! ```python classifier( ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] ) -``` +``` ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}, {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 +默认情况下,此 pipeline 加载选定的预训练模型,该模型已针对英语情感分析进行了微调。创建 `classifier` 对象时,模型将被下载和缓存。如果你重新运行该命令,则将使用缓存的模型,而无需再次下载模型。 -将一些文本传递到pipeline时涉及三个主要步骤: +将一些文本传递到 pipeline 时涉及三个主要步骤: 1. 文本被预处理为模型可以理解的格式。 -2. 预处理的输入被传递给模型。 -3. 模型处理后输出最终人类可以理解的结果。 +2. 将预处理后的输入传递给模型。 +3. 对模型的预测进行后续处理并输出最终人类可以理解的结果。 -目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: +目前 [可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines) 有: -* `feature-extraction`(获取文本的向量表示) -* `fill-mask` -* `ner`(命名实体识别) -* `question-answering` -* `sentiment-analysis` -* `summarization` -* `text-generation` -* `translation` -* `zero-shot-classification` +* `eature-extraction` (获取文本的向量表示) +* `fill-mask` (完形填空) +* `ner` (命名实体识别) +* `question-answering` (问答) +* `sentiment-analysis` (情感分析) +* `summarization` (提取摘要) +* `text-generation` (文本生成) +* `translation` (翻译) +* `zero-shot-classification` (零样本分类) -让我们来看看其中的一些吧! +让我们来看看其中的一些例子吧! ## 零样本分类 [[零样本分类]] -我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 + +我们将从一个具挑战性的任务开始,我们需要对没有标签的文本进行分类。这是实际项目中的常见场景,因为给文本打标签通常很耗时并且需要领域专业知识。对于这项任务 `zero-shot-classification`。pipeline 非常强大:它允许你直接指定用于分类的标签,因此你不必依赖预训练模型的标签。下面的模型展示了如何使用这三个标签将句子分类为 `education(教育)` 、 `politics(政治)` 、或者 `business(商业)` —— 也可以使用你喜欢的任何其他标签集对文本进行分类。 ```python from transformers import pipeline @@ -96,13 +102,17 @@ classifier( 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} ``` -此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! +这个 pipeline 称为 `zero-shot` (零样本学习),因为你不需要对数据上的模型进行微调即可使用它。它可以直接返回你想要的任何标签列表的概率分数! + -✏️**快来试试吧!** 使用您自己的序列和标签,看看模型的行为。 + +✏️**快来试试吧!**使用你自己的序列和标签,看看模型的表现如何。 + ## 文本生成 [[文本生成]] -现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 + +让我们尝试一下另一个 pipeline,这次是用于生成文本的 `text-generation` 。这里的主要使用方法是你提供一些文本,模型将通过生成剩余的文本来自动补全整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果你没有得到相同的如下所示的结果也是正常的。 ```python from transformers import pipeline @@ -117,16 +127,19 @@ generator("In this course, we will teach you how to") 'data flows — data flows of various types, as seen by the ' 'HTTP'}] ``` -您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 +你可以使用参数 `num_return_sequences` 控制生成多少个不同的候选的句子,并使用参数 `max_length` 控制输出文本的总长度。 -✏️**快来试试吧!** 使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 + +✏️**快来试试吧!**使用 `num_return_sequences` 和 `max_length` 参数生成两个句子,每个句子 15 个单词。 + -## 在pipeline中使用 Hub 中的其他模型 [[在pipeline中使用 Hub 中的其他模型]] -前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 +## 在 pipeline 中使用 Hub 中的其他模型 [[在 pipeline 中使用 Hub 中的其他模型]] + +前面的示例使用了默认模型,但你也可以从 Hub 中选择一个特定模型,将其用于特定任务,例如文本生成。转到 [模型中心(hub)](https://huggingface.co/models) 并单击左侧的相应标签将会只显示该任务支持的模型。你应该看到一个 [模型筛选](https://huggingface.co/models?pipeline_tag=text-generation) 的页面。 -让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: +让我们试试 [`distilgpt2`](https://huggingface.co/distilgpt2) 模型吧!以下面是在之前的 pipeline 中加载它的方法: ```python from transformers import pipeline @@ -145,20 +158,27 @@ generator( 'practice realtime, and with a hands on experience on both real ' 'time and real'}] ``` -您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 -通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 +你可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)也包含了支持多种语言的多语言模型。 + +通过单击选择模型后,你会看到有一个小组件,可让你直接在线试用。通过这种方式,你可以在下载之前快速测试模型的功能。 + -✏️**快来试试吧!** 使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! + +✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在 pipeline 中使用它! + ## 推理 API [[推理 API]] -所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 -小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 +所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/) 上找到。通过输入自定义文本并观察模型的输出,你可以直接在此页面上使用模型。 + +小组件形式的推理 API 也可作为付费产品使用,如果你的工作流程需要它,它会派上用场。有关更多详细信息,请参阅 [定价页面](https://huggingface.co/pricing) 。 + +## 完形填空 [[Mask filling]] + +你将尝试的下一个 pipeline 是 `fill-mask` 。此任务的想法是填补给定文本中的空白: -## Mask filling [[Mask filling]] -您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: ```python from transformers import pipeline @@ -175,14 +195,19 @@ unmasker("This course will teach you all about models.", top_k=2) 'token': 38163, 'token_str': ' computational'}] ``` -**top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 + + `top_k` 参数控制要显示的结果有多少种。请注意,这里模型填补了特殊的 `` 词,它通常被称为 `mask token` 。不同的 `mask-filling` 模型可能有不同的 `mask token` ,因此在探索其他模型时要验证正确的 `mask token` 是什么。检查它的一种方法是查看小组件中使用的 `mask token` 。 -✏️**快来试试吧!** 在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? + +✏️**快来试试吧!**在 Hub 上搜索 `bert-base-cased` 模型并在推理 API 小组件中找到它的 mask token。对于上面 pipeline 示例中的句子,这个模型预测了什么? + ## 命名实体识别 [[命名实体识别]] -命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: + +命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: + ```python from transformers import pipeline @@ -195,16 +220,21 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} ] ``` -在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 -我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 +在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 + +我们在创建 pipeline 的函数中传递的 `grouped_entities=True` 参数告诉 pipeline 将与同一实体对应的句子部分重新分组:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如, `Sylvain` 分割为了四部分: `S、##yl、##va` 和 `##in` 。在后处理步骤中,pipeline 成功地重新组合了这些部分。 -✏️**快来试试吧!** 在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? + +✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。对于上面示例中的句子,这个词性标注的模型预测了什么? + -## 问答系统 [[问答系统]] -问答pipeline使用来自给定上下文的信息回答问题: +## 问答 [[问答]] + + `question-answering` pipeline 使用给定上下文中的信息回答问题: + ```python from transformers import pipeline @@ -216,14 +246,12 @@ question_answerer( ``` ```python out {'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} -klyn", -) - ``` -请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 +请注意,这个 pipeline 通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 + +## 提取摘要 [[文本摘要]] -## 文本摘要 [[文本摘要]] -文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: +提取摘要是将文本缩减为较短文本的任务,同时保留文本中所有(或大部分)主要(重要)的信息。下面是一个例子: ```python from transformers import pipeline @@ -261,10 +289,12 @@ summarizer( 'industrial countries in Europe and Asia, continue to encourage ' 'and advance engineering .'}] ``` -与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 + +与文本生成一样,你指定结果的 `max_length` 或 `min_length` 。 ## 翻译 [[翻译]] -对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: + +对于翻译,如果你在任务名称中包含语言对(例如“ `translation_en_to_fr` ”),则可以使用默认模型,但最简单的方法是在 [模型中心(hub)](https://huggingface.co/models) 选择要使用的模型。在这里,我们将尝试从法语翻译成英语: ```python from transformers import pipeline @@ -277,12 +307,12 @@ translator("Ce cours est produit par Hugging Face.") ``` -与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 +与文本生成和摘要一样,你可以指定结果的 `max_length` 或 `min_length` 。 -✏️**快来试试吧!** 搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 +✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前面的句子翻译成几种不同的语言。 -到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file +到目前为止显示的 pipeline 主要用于演示目的。它们是为特定任务而定制的,不能对他们进行自定义的修改。在下一章中,你将了解 `pipeline()` 函数内部的过程以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx index c056b2b10..3e75f3660 100644 --- a/chapters/zh-CN/chapter1/4.mdx +++ b/chapters/zh-CN/chapter1/4.mdx @@ -1,64 +1,64 @@ -# Transformers 是如何工作的? [[Transformers 是如何工作的?]] +# Transformers 是如何工作的?[[Transformers 是如何工作的?]] -在本节中,我们将深入了解 Transformer 模型的架构。 +在本节中,我们简要了解 Transformer 模型的架构。 -## 一点Transformers的发展历史 [[一点Transformers的发展历史]] +## 一点 Transformers 的发展历史 [[一点Transformers的发展历史]] -以下是 Transformer 模型(简短)历史中的一些关键节点: +以下是 Transformer 模型(简要)历史中的一些关键节点:
A brief chronology of Transformers models.
-[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + [Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月提出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf) ,第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 -- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805) ,另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) -- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf) ,GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 -- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108) ,BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683) ,两个使用与原始 Transformer 模型原始架构的大型预训练模型(第一个这样做) -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165) ,GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: +此列表远非详尽无遗,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) +- GPT-like (也被称作自回归 Transformer 模型) +- BERT-like (也被称作自动编码 Transformer 模型) +- BART/T5-like (也被称作序列到序列的 Transformer 模型) 稍后我们将更深入地探讨这些分类。 -## Transformers是语言模型 [[Transformers是语言模型]] +## Transformers 是语言模型 [[Transformers是语言模型]] 上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 +这种类型的模型可以对其训练过的语言有统计学的理解,但对于特定的实际任务的效果并不是很好。因此,一般的预训练模型会经历一个称为迁移学习(transfer learning)的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 +任务的一个例子是阅读 `n` 个单词的句子,预测下一个单词。这被称为因果语言建模(causal language modeling),因为输出取决于过去和现在的输入,而不依赖于未来的输入。
Example of causal language modeling in which the next word from a sentence is predicted.
-另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 +另一个例子是掩码语言建模(masked language modeling),俗称完形填空,该模型预测句子中的遮住的词。
Example of masked language modeling in which a masked word from a sentence is predicted.
-## Transformer是大模型 [[Transformer是大模型]] +## Transformer 是大模型 [[Transformer是大模型]] 除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 @@ -75,19 +75,19 @@ -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 +这只是显示了一支团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响。如果为了获得最佳超参数而进行大量试验,所造成的碳排放当量会更高。 想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! 这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 -顺便说一句,您可以通过多种工具评估模型训练的碳排放。 例如 [ML CO2 Impact](https://mlco2.github.io/impact/) 或集成在 🤗 Transformers 中的 [Code Carbon]( https://codecarbon.io/)。 要了解更多相关信息,您可以阅读这篇[博客文章](https://huggingface.co/blog/carbon-emissions-on-the-hub),其中将向您展示如何生成 `emissions.csv` 文件估计训练的碳足迹,这里还有 🤗 Transformers 关于碳足迹的[文档](https://huggingface.co/docs/hub/model-cards-co2)。 +顺便说一句,你可以通过多种工具评估模型训练的碳排放。例如 [ML CO2 Impact](https://mlco2.github.io/impact/) 或集成在 🤗 Transformers 中的 [Code Carbon]( https://codecarbon.io/) 。要了解更多相关信息,你可以阅读这篇 [博客文章](https://huggingface.co/blog/carbon-emissions-on-the-hub) ,其中将向你展示如何生成 `emissions.csv` 文件估计训练的碳排放,这里还有 🤗 Transformers 关于碳排放的 [文档](https://huggingface.co/docs/hub/model-cards-co2) 。 ## 迁移学习 [[迁移学习]] -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 +预训练(Pretraining)是是指从头开始训练模型:随机初始化权重,在没有任何先验知识的情况下开始训练。
The pretraining of a language model is costly in both time and money. @@ -96,13 +96,13 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: +另一方面,微调(Fine-tuning)是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集进行额外的训练。等等,为什么不直接为实际的最终任务而训练模型呢?有几个原因: -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于 NLP 问题,预训练模型将对你在任务中使用的语言有某种统计规律上的理解)。 * 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 * 出于同样的原因,获得好结果所需的时间和资源要少得多 -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 +例如,可以利用英语的预训练过的模型,然后在 arXiv 语料库上对其进行微调,从而形成一个基于科学研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为迁移学习(transfer learning)。
The fine-tuning of a language model is cheaper than pretraining in both time and money. @@ -111,19 +111,19 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型——一个尽可能接近你手头的任务的模型——并对其进行微调。 -## 一般的体系结构 [[一般的体系结构]] -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 +## Transformer 模型的通用架构 [[Transformer 模型的通用架构]] +在本节中,我们将概述 Transformer 模型的通用架构。如果你不理解其中的一些概念,不用担心;后面的章节将详细介绍每个组件。 -## 介绍 [[介绍]] +## 简介 [[简介]] 该模型主要由两个块组成: -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 +* **Encoder (左侧)**:编码器接收输入并构建其表示(特征)。这意味着模型的使命是从输入中获取理解。 +* **Decoder (右侧)**:解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着模型的使命是生成输出。
Architecture of a Transformers models @@ -132,47 +132,45 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这些部件中的每一个都可以独立使用,具体取决于任务: -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 +* **Encoder-only 模型**:适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only 模型**:适用于生成任务,如文本生成。 +* **Encoder-decoder 模型** 或者 **sequence-to-sequence 模型**:适用于需要根据输入进行生成的任务,如翻译或摘要。 在后面的部分中,我们将单独地深入研究这些体系结构。 ## 注意力层 [[注意力层]] -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 +Transformer 模型的一个关键特性是*注意力层*。事实上,提出 Transformer 架构的文章的标题是 [“Attention Is All You Need”](https://arxiv.org/abs/1706.03762) !我们将在课程的后面更加深入地探讨注意力层的细节;现在,你需要知道的是,这一层将告诉模型在处理每个单词的表示时,对不同单词的重视(忽略)程度。 -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 +可以考虑一个例子,假如我们要将英语翻译成法语。给定输入“You like this course”时,翻译模型需要注意相邻的词“You”,才能获得“like”的正确翻译,因为在法语中,动词“like”的变形依赖于主语。然而,对于那个词的翻译,句子的其余部分并没有用。同样,当翻译“this”时,模型也需要注意词“course”,因为“this”的翻译依赖于关联名词是单数还是复数。再次,句子中的其他词对于“course”的翻译并不重要。对于更复杂的句子(和更复杂的语法规则),模型可能需要特别注意可能在句子中更远处出现的词,以正确翻译每个词。 同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 +现在你对注意力层是什么有了一些了解,让我们更详细地看一下 Transformer 架构。 -## 原始的结构 [[原始的结构]] +## Transformer 的原始结构 [[原始的结构]] -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 +Transformer 架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输出来尝试预测第四个单词。 -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 +为了在训练过程中加快速度(当模型可以访问目标句子时),会将整个目标输入解码器,但不允许获取到要翻译的单词(如果它在尝试预测位置 2 的单词时可以访问位置 2 的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第 4 个单词时,注意力层只能获取位置 1 到 3 的单词。 -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: +最初的 Transformer 架构如下所示,编码器位于左侧,解码器位于右侧:
Architecture of a Transformers models
-注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二个注意力层只使用编码器的输出。因此,它在预测当前单词时,可以使用整个句子的信息。这是非常有用的,因因为不同的语言可以有把词放在不同顺序的语法规则,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 +也可以在编码器/解码器中使用*attention mask(注意力掩码层)*,以防止模型关注到某些特殊单词。例如,用于在批量处理句子时使所有输入长度一致的特殊填充词。 -## 架构与参数 [[架构与参数]] +## architecture(架构)与 checkpoints(权重参数又称检查点) [[architecture(架构)与 checkpoints(权重参数又称检查点)]] -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: +在本课程中,当我们深入探讨 Transformers 模型时,你将看到架构、参数和模型。这些术语的含义略有不同: -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 +* **architecture(架构)**:这是模型的骨架 —— 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints(权重参数又称检查点)**:这些是将在给架构中结构中加载的权重参数,是一些具体的数值。 +* **Model(模型)**:这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." +例如,BERT 是一个架构,而 `bert-base-cased` ,这是谷歌团队为 BERT 的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT 模型”和“ `bert-base-cased` 模型。” diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx index 64b30d2d7..ee388deba 100644 --- a/chapters/zh-CN/chapter1/5.mdx +++ b/chapters/zh-CN/chapter1/5.mdx @@ -7,16 +7,16 @@ -“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 +编码器模型仅使用 Transformer 模型的编码器部分。在每次计算过程中,注意力层都能访问整个句子的所有单词,这些模型通常具有“双向”(向前/向后)注意力,被称为自编码模型。 -这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 +这些模型的预训练通常会使用某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 -“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 +“编码器”模型适用于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 该系列模型的典型代表有: -- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html) -- [BERT](https://huggingface.co/transformers/model_doc/bert.html) -- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html) -- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html) -- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html) +- [ALBERT](https://huggingface.co/transformers/model_doc/albert) +- [BERT](https://huggingface.co/transformers/model_doc/bert) +- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert) +- [ELECTRA](https://huggingface.co/transformers/model_doc/electra) +- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta) diff --git a/chapters/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx index 450d827f1..2d5006304 100644 --- a/chapters/zh-CN/chapter1/6.mdx +++ b/chapters/zh-CN/chapter1/6.mdx @@ -7,16 +7,15 @@ -“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 +“解码器”模型仅使用 Transformer 模型的解码器部分。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 “解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 -这些模型最适合于涉及文本生成的任务。 +这些模型最适合处理文本生成的任务。 该系列模型的典型代表有: - -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt) -- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transfor-xl.html) +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl) +- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfor-xl) diff --git a/chapters/zh-CN/chapter1/7.mdx b/chapters/zh-CN/chapter1/7.mdx index 5e8b7c084..010e3af72 100644 --- a/chapters/zh-CN/chapter1/7.mdx +++ b/chapters/zh-CN/chapter1/7.mdx @@ -1,4 +1,4 @@ -# 序列到序列模型 [[序列到序列模型]] +# 编码器-解码器模型 [[编码器-解码器模型]] -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 +编码器-解码器模型(也称为序列到序列模型)同时使用 Transformer 架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问输入句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常会更加复杂。例如, [T5](https://huggingface.co/t5-base) 通过用单个掩码特殊词替换随机文本范围(可能包含多个词)进行预训练,然后目标是预测被遮盖单词原始的文本。 序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 该系列模型的典型代表有: -- [BART](https://huggingface.co/transformers/model_doc/bart.html) -- [mBART](https://huggingface.co/transformers/model_doc/mbart.html) -- [Marian](https://huggingface.co/transformers/model_doc/marian.html) -- [T5](https://huggingface.co/transformers/model_doc/t5.html) +- [BART](https://huggingface.co/transformers/model_doc/bart) +- [mBART](https://huggingface.co/transformers/model_doc/mbart) +- [Marian](https://huggingface.co/transformers/model_doc/marian) +- [T5](https://huggingface.co/transformers/model_doc/t5) diff --git a/chapters/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx index 641590288..0929d80f8 100644 --- a/chapters/zh-CN/chapter1/8.mdx +++ b/chapters/zh-CN/chapter1/8.mdx @@ -7,9 +7,9 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section8.ipynb"}, ]} /> -如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 +如果你打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的所有文字内容,中间可能夹带一些意识形态或者价值观的刻板印象。 -为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: +为了快速解释清楚这个问题,让我们回到一个使用 BERT 模型的 pipeline 的例子: ```python from transformers import pipeline @@ -26,6 +26,6 @@ print([r["token_str"] for r in result]) ['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] ['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] ``` -当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 +当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管 BERT 是少数使用经过筛选和清洗后,并且看似中立的数据集上建立的的 Transformer 模型,而不是通过从互联网上搜集数据(它使用的是 [Wikipedia 英文](https://huggingface.co/datasets/wikipedia) 和 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 数据集),但是这种情况还是发生了。 -因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file +因此,当你使用这些工具时,你需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/9.mdx b/chapters/zh-CN/chapter1/9.mdx index 286a98fed..27061d5a6 100644 --- a/chapters/zh-CN/chapter1/9.mdx +++ b/chapters/zh-CN/chapter1/9.mdx @@ -5,12 +5,12 @@ classNames="absolute z-10 right-0 top-0" /> -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 +在本章中,你了解了如何使用来自🤗Transformers 的高级函数 `pipeline()` 处理不同的 NLP 任务。你还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理 API 直接在浏览器中测试模型。 -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: +我们从最终的效果的角度讨论了 Transformer 模型的工作方式,并讨论了迁移学习和微调的重要性。一个关键的收获是:你可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于你要解决的任务类型。下表总结了这一点: | 模型 | 示例 | 任务| | ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| -| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file +| 编码器 | ALBERT,BERT,DistilBERT,ELECTRA,RoBERTa |句子分类、命名实体识别、抽取式问答(从文本中提取答案)| +| 解码器 | CTRL,GPT,GPT-2,Transformer XL |文本生成| +| 编码器-解码器 | BART,T5,Marian,mBART |文本摘要、翻译、生成式问答(生成问题的回答类似 chatgpt)| \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index 4f7f407bc..01f4c1523 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -5,18 +5,19 @@ classNames="absolute z-10 right-0 top-0" /> -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 +正如你在 [第一章](/course/chapter1) ,中看到的那样,Transformers 模型通常规模庞大。包含数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。再者,新模型的推出几乎日新月异,而每种模型都有其独特的实现方式,尝试全部模型绝非易事。 -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有模型的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**类,可以像在其各自的机器学习 (ML) 框架中的任何其他模型一样方便地进行处理。 -- **简单**:库中几乎没有任何抽象。 “All in one file”(所有代码在一个文件中)是一个核心概念:模型的前向传播完全定义在一个文件中,因此代码本身易于理解和修改 +🤗 Transformers 库应运而生,就是为了解决这个问题。它的目标是提供一个统一的 API 接口,通过它可以加载、训练和保存任何 Transformer 模型。该库的主要特点有: +- **易于使用**:仅需两行代码,就能下载、加载并使用先进的 NLP 模型进行推理。 +- **灵活**:在本质上,所有的模型都是简单的 PyTorch nn.Module 或 TensorFlow tf.keras.Model 类,并可像在各自的机器学习(ML)框架中处理其他模型一样处理它们。 +- **简单**:该库几乎没有进行任何抽象化。🤗 Transformers 库一个核心概念是“全在一个文件中”:模型的前向传播完全在一个文件中定义,这使得代码本身易于理解和修改。 -最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的网络结构(layers)。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 +最后一个特性使🤗 Transformers 与其他 ML 库截然不同。模型并非建立在跨越多个代码文件共享的模块上;相反,每一个模型都有自己的层次结构。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 -本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和*tokenizer*分词器来复制[Chapter 1](/course/chapter1)中引入的函数`pipeline()`. 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 +本章将从一个端到端(从输入端到输出端)的示例开始,在该示例中,我们一起使用模型和 tokenizer 来复刻 [第一章](/course/chapter1) 中看到的 `pipeline()` 函数。接下来,我们将讨论 `Model` API:我们将深入研究 `Model` 类和 `Config` 类,并向你展示如何加载模型,以及它如何将输入处理为输出。 + +然后我们来看看 `tokenizer` API,它是 `pipeline()` 函数的另一个重要组成部分。在 `pipeline()` 中 `Tokenizer` 负责第一步和最后一步的处理,将文本转换到神经网络的输入,以及在需要时将其转换回文本。最后,我们将向你展示如何处理将多个句子整理为一个 batch 发送给模型,然后我们将更深入地研究 `tokenizer()` 函数。 -接下来,我们将介绍 *tokenizer* API,它是 `pipeline()` 函数的另一个主要组件。*tokenizer* 负责第一个和最后一个处理步骤,处理从文本到神经网络数值输入的转换,以及在需要时将数值转换回文本。最后,我们将向您展示如何处理将多个句子作为一个准备好的批次发送到模型中,然后通过更深入地了解高级 `tokenizer()` 函数来总结所有内容。 -⚠️ 为了从Model Hub和🤗Transformers的所有可用功能中获益,我们建议创建帐户. +⚠️ 为了充分利用 Model Hub 和🤗 Transformers 提供的所有功能,我们建议你创建一个账户 \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index c0242bb79..da9a4b5fe 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -1,6 +1,6 @@ -# 管道的内部 [[管道的内部]] +# Pipeline 的内部 [[Pipeline 的内部]] {#if fw === 'pt'} @@ -23,7 +23,7 @@ {/if} -这是第一部分,根据您使用PyTorch或者TensorFlow,内容略有不同。点击标题上方的平台,选择您喜欢的平台! +这是第一部分,根据你使用 PyTorch 或者 TensorFlow,内容略有不同。点击标题上方的平台,选择你喜欢的平台! {#if fw === 'pt'} @@ -32,7 +32,7 @@ {/if} -让我们从一个完整的示例开始,看看在[Chapter 1](/course/chapter1)中执行以下代码时在幕后发生了什么 +让我们从一个完整的示例开始,看看在 [第一章](/course/chapter1) 中执行以下代码时在幕后发生了什么 ```python from transformers import pipeline @@ -46,37 +46,36 @@ classifier( ) ``` -获得: +获得如下输出: ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}, {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -正如我们在[Chapter 1](/course/chapter1)中看到的,这个 pipeline 集成了三个步骤:预处理、模型推理和后处理: +正如我们在 [第一章](/course/chapter1) 中看到的,这个 pipeline 集成了三个步骤:预处理、模型计算和后处理:
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head.
-让我们快速浏览一下这些内容。 +让我们先简单了解一下这三个步骤。 -## 使用分词器进行预处理 [[使用分词器进行预处理]] +## 使用 tokenizer( tokenizer )进行预处理 [[使用 tokenizer( tokenizer )进行预处理]] -与其他神经网络一样,Transformer模型无法直接处理原始文本, 因此pipeline的第一步是将文本输入转换为模型能够理解的数字。 为此,我们使用*tokenizer*(分词器),负责: +与其他神经网络一样,Transformer 模型无法直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型能够理解的数字。为此,我们使用 tokenizer( tokenizer ),它将负责: -- 将输入拆分为单词、子单词或符号(如标点符号),称为标记(*token*) -- 将每个标记(token)映射到一个整数 -- 添加其他可能对模型有用的输入:例如,位置编码等信息。 -- - 位置编码:指示每个词元在句子中的位置。 +- 将输入拆分为单词、子单词或符号(如标点符号),称为 **token**(标记) +- 将每个标记(token)映射到一个数字,称为 **input ID**(inputs ID) +- 添加模型需要的其他输入,例如特殊标记(如 `[CLS]` 和 `[SEP]` ) +- - 位置编码:指示每个标记在句子中的位置。 - - 段落标记:区分不同段落的文本。 - - 特殊标记:例如 [CLS] 和 [SEP] 标记,用于标识句子的开头和结尾。 +在使用模型时所有这些预处理都需要与模型预训练时的方式完全相同,因此我们首先需要从 [Model Hub](https://huggingface.co/models) 中下载这些信息。为此,我们使用 `AutoTokenizer` 类和它的 `from_pretrained()` 方法,并输入我们模型 checkpoint 的名称,它将自动获取与模型的 tokenizer 相关联的数据,并对其进行缓存(因此只有在你第一次运行下面的代码时才会下载)。 -所有预处理都需要以与模型预训练时完全相同的方式完成(保持分词方式的一致性非常重要,可以确保模型的性能和准确性),因此我们首先需要从[Model Hub](https://huggingface.co/models)中下载这些信息。为此,我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称,它将自动获取与模型的标记器相关联的数据,并对其进行缓存(因此只有在您第一次运行下面的代码时才会下载)。 - -因为`sentiment-analysis`(情绪分析)Pipeline的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`(你可以看到它的模型介绍页[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)),我们运行以下程序: +`sentiment-analysis` (情绪分析)管道默认的 checkpoint 是 `distilbert-base-uncased-finetuned-sst-2-english` (你可以在 [这里](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english) )看到它的模型卡片,我们运行以下代码: ```python from transformers import AutoTokenizer @@ -85,11 +84,11 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" tokenizer = AutoTokenizer.from_pretrained(checkpoint) ``` -一旦我们有了tokenizer,就可以直接将句子传递给它,然后我们会得到一个字典,它包含了可以输入到模型的数据。接下来,我们只需要将输入 ID 列表转换为张量即可。 +当我们有了 tokenizer,我们就可以直接将我们的句子传递给它,我们就会得到一个 `input ID(inputs ID)` 的列表!剩下要做的唯一一件事就是将 input ID 列表转换为 tensor(张量)。 -使用 🤗 Transformers 时,您无需担心它背后的机器学习框架,它可以是 PyTorch、TensorFlow或 Flax。不过,Transformer 模型只接受*张量*作为输入。如果您不熟悉张量,可以把它看作 NumPy 数组。NumPy 数组可以是标量 (0D)、向量 (1D)、矩阵 (2D) 或更高维度的数组,本质上就是一个张量。其他机器学习框架中的张量也类似,通常与 NumPy 数组一样容易创建。 +你在使用🤗 Transformers 时,您无需担心它背后的机器学习框架;它可能是 PyTorch 或 TensorFlow,或 Flax。但是,Transformers 模型只接受 `tensor(张量)` 作为输入。如果这是你第一次听说 tensor,你可以把它们想象成 NumPy 数组。NumPy 数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。这些都可以称为 tensor;其他 ML 框架的 tensor 使用方法也类似,通常与 NumPy 数组一样容易创建。 -要指定要返回的张量类型(PyTorch、TensorFlow或plain NumPy),我们使用`return_tensors`参数: +我们可以使用 `return_tensors` 参数指定我们想要得到的 tensor 的类型(PyTorch、TensorFlow 或纯 NumPy), {#if fw === 'pt'} ```python @@ -111,11 +110,11 @@ print(inputs) ``` {/if} -暂时不用担心填充和截断,我们稍后会解释它们。这里主要需要记住的是,您可以传入单个句子或句子列表,并指定想要返回的张量类型(如果不指定类型,则会返回嵌套列表)。 +现在不要担心 padding(填充)和 truncation(截断);我们稍后会解释这些。这里要记住的是,你可以传递一个句子或一组句子,还可以指定要返回的 tensor 类型(如果没有传递类型,默认返回的是 python 中的 list 格式)。 {#if fw === 'pt'} -以下是PyTorch张量的结果: +以下是 PyTorch 张量的结果: ```python out { @@ -131,7 +130,7 @@ print(inputs) ``` {:else} -以下是TensorFlow张量的结果: +以下是 TensorFlow 张量的结果: ```python out { @@ -149,11 +148,13 @@ print(inputs) ``` {/if} -输出本身是一个包含两个键的字典,`input_ids` 和 `attention_mask`。 `input_ids` 包含两行整数(每个句子一行),它们是每个句子中各个 token 的唯一标识符。我们将在本章后面解释 `attention_mask` 的作用。 -## 模型推理 [[模型推理]] +输出是一个包含两个键, `input_ids` 和 `attention_mask` 。 `input_ids` 包含两行整数(每个句子一行),它们是每个句子中 token 的 ID。我们将在本章后面解释什么是 `attention_mask` 。 + +## 探索模型 [[探索模型]] {#if fw === 'pt'} -我们可以像使用*tokenizer*一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类也有`from_pretrained()`方法: + +我们可以像使用 tokenizer 一样下载预训练模型。Transformers 提供了一个 `AutoModel` 类,它也有一个 `from_pretrained()` 方法: ```python from transformers import AutoModel @@ -162,33 +163,36 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModel.from_pretrained(checkpoint) ``` {:else} -我们可以像使用*tokenizer*一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类也有`from_pretrained()`方法: + +我们可以像使用 tokenizer 一样下载预训练模型。Transformers 提供了一个 `TFAutoModel` 类,它也有一个 `from_pretrained()` 方法: ```python -from transformers import AutoModel +from transformers import TFAutoModel checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModel.from_pretrained(checkpoint) +model = TFAutoModel.from_pretrained(checkpoint) ``` {/if} -在这段代码中,我们加载了之前在 pipeline 中使用的相同检查点(实际上它应该已经被缓存)并用它实例化了一个模型。 +在这段代码中,我们将之前在 pipeline 中使用的 checkpoint(实际上应该已经被缓存了)下载下来,并用它实例化了一个模型。 + +这个模型只包含基本的 Transformer 模块:输入一些句子,它输出我们将称为 `hidden states(隐状态)` ,也被称为特征。每个输入,我们都可以获取一个高维向量,代表 Transformer 模型对该输入的上下文理解。 + +如果这有些难以理解,不要担心。我们以后再解释。 -这个架构只包含基本的 Transformer 模块:给定一些输入,它会输出我们称之为*隐藏状态 (hidden states)* 的内容,也称为*特征 (features)*。对于每个模型输入,我们将检索一个高维向量,该向量表示 **Transformer 模型对该输入的上下文理解**。 -如果你现在对隐藏状态和上下文理解的概念还不理解,也不用担心,我们会在后续的内容中详细解释这些概念,并举例说明它们是如何工作的。 +这些隐状态本身就很有用,它们被称为模型头(head),通常是模型另一部分的输入。在 [第一章](/course/chapter1) 中,可以使用相同的体系结构的模型执行不同的任务,这是因为每个任务都有一个对应的模型头。 -虽然这些隐藏状态本身可能很有用,但它们通常作为下游任务的输入,例如模型*头部(head)*的输入。在[Chapter 1](/course/chapter1)中,我们介绍了可以使用相同的架构执行不同的任务,但每个任务都会有不同的头部与之关联。 -### 高维向量? [[高维向量?]] +### 高维向量?[[高维向量?]] -Transformers模块的向量输出通常较大。它通常有三个维度: +Transformers 模块的矢量输出通常较大。它通常有三个维度: -- **批次大小(Batch size)**: 一次处理的序列数(在我们的示例中为2)。 -- **序列长度 (Sequence length)**: 序列的数值表示的长度(在我们的示例中为16)。 -- **隐藏层维度(Hidden size)**: 每个模型输入的向量维度。 +- **Batch size**(批次大小):一次处理的序列数(在我们的示例中为 2)。 +- **Sequence length**(序列长度):表示序列(句子)的长度(在我们的示例中为 16)。 +- **Hidden size**(隐藏层大小):每个模型输入的向量维度。 -之所以说它是“高维度”的,是因为最后一个维度值。隐藏层维度可以非常大(对于较小的模型768 是常见的,而在较大的模型中,它可以达到 3072 或更多)。 +之所以说它是“高维度”的,是因为最后一个维度值。隐藏层维度可以非常大(对于较小的模型,常见的是 768,对于较大的模型,这个数字可以达到 3072 或更多)。 -我们可以通过将预处理后的输入数据传递给模型来验证这一点: +如果我们将预处理的之后的值输入到模型中,我们会得到以下输入: {#if fw === 'pt'} ```python @@ -210,37 +214,38 @@ print(outputs.last_hidden_state.shape) ``` {/if} -🤗 Transformers 模型的输出类似于 `namedtuple` 或字典。您可以通过属性 (就像我们之前所做的那样) 或键 (例如` outputs["last_hidden_state"]`) 来访问元素,甚至可以通过索引访问元素 (例如 `outputs[0]`),前提是您知道要查找的内容的位置。 -### 模型头:数值的意义 [[模型头:从数值中提取意义]] +注意,🤗 Transformers 模型的输出有些像 `namedtuple` 或词典。你可以通过使用“.”+属性(就像我们在上面示例中所做的那样)或键( `outputs["last_hidden_state"]` )访问元素,如果你确切知道要查找的内容的位置( `outputs[0]` ),也可以通过索引访问元素。 -模型头接收隐藏状态的高维向量作为输入,并将其映射到另一个维度。它们通常由一个或几个线性层构成: +### 模型头:理解数字的意义 [[模型头:理解数字的意义]] + +Transformers 模型的输出会直接发送到模型头进行处理。 + +模型头通常由一个或几个线性层组成,它的输入是隐状态的高维向量,它会并将其投影到不同的维度。
A Transformer network alongside its head.
-Transformers模型的输出直接发送到模型头进行处理。 +在此图中,模型由其嵌入层和后续层表示。嵌入层将 tokenize 后输入中的每个 inputs ID 转换为表示关联 token 的向量。后续层使用注意机制操纵这些向量,生成句子的最终表示。 -在此图中,模型由其嵌入层和后续层表示。嵌入层将标记化输入中的每个输入ID转换为表示关联标记(token)的向量。后续层使用注意机制操纵这些向量,以生成句子的最终表示。 +Transformers 中有许多不同的体系结构,每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表: -🤗 Transformers中有许多不同的体系结构,每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表: - -- `*Model` (retrieve the hidden states) -- `*ForCausalLM` -- `*ForMaskedLM` -- `*ForMultipleChoice` -- `*ForQuestionAnswering` -- `*ForSequenceClassification` -- `*ForTokenClassification` +- `*Model` (隐状态检索) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` - 以及其他 🤗 {#if fw === 'pt'} -对于我们的示例,我们需要一个带有序列分类头部的模型(能够将句子分类为肯定或否定)。 因此,我们实际上不会使用 `AutoModel` 类,而是使用 `AutoModelForSequenceClassification`(`AutoModel` 类只包含基本的 Transformer 模块,它可以提取文本的特征,但不能进行分类。 -`AutoModelForSequenceClassification` 类在 `AutoModel` 的基础上添加了一个序列分类头部,可以将文本分类为不同的类别。 -): +以情感分类为例,我们需要一个带有序列分类头的模型(能够将句子分类为积极或消极)。因此,我们不选用 `AutoModel` 类,而是使用 `AutoModelForSequenceClassification` 。也就是说前面写的 `model = AutoModel.from_pretrained(checkpoint)` 并不能得到情感分类任务的结果,因为没有加载 Model head。 +`AutoModelForSequenceClassification` 类在 `AutoModel` 的基础上添加了一个序列分类头部,可以 +将文本分类为不同的类别。 ```python from transformers import AutoModelForSequenceClassification @@ -249,7 +254,8 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint) outputs = model(**inputs) ``` {:else} -For our example, we will need a model with a sequence classification head (to be able to classify the sentences as positive or negative). So, we won't actually use the `TFAutoModel` class, but `TFAutoModelForSequenceClassification`: + +以情感分类为例,我们需要一个带有序列分类头的模型(能够将句子分类为积极或消极)。因此,我们不选用 `TFAutoModel` 类,而是使用 `TFAutoModelForSequenceClassification` 。也就是说前面写的 model = TFAutoModel.from_pretrained(checkpoint)并不能得到情感分类任务的结果,因为没有加载 Model head。 ```python from transformers import TFAutoModelForSequenceClassification @@ -260,8 +266,9 @@ outputs = model(inputs) ``` {/if} -现在,如果我们观察输出的shape,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): -正如您所见,输出向量的尺寸与输入向量相比要小得多。这是因为模型头将输入向量中的信息压缩成两个值,每个标签一个 +如果我们看一下现在输出的形状,其维度会降低很多:模型头接收我们之前看到的高维向量作为输入,并输出包含两个值(每种标签一个)的向量。正如您所见,输出向量的尺寸与输入向量相比要小得多。这是因为模型头将输入向量中的信息压缩成两个值,每个标签一个 +264 ```python 265 ```python: + ```python print(outputs.logits.shape) ``` @@ -280,11 +287,11 @@ torch.Size([2, 2]) {/if} -因为我们只有两个句子和两个标签,所以我们从模型中得到的结果是2 x 2的维度。 +由于我们只有两个句子和两钟标签,所以我们从模型中得到的结果的形状是 2 x 2。 -## 对输出进行后处理 [[对输出进行后处理]] +## 对输出进行后序处理 [[对输出进行后处理]] -我们从模型中得到的输出值本身并不一定有意义。我们来看看, +我们从模型中得到的输出值本身并不一定有意义。我们来看看, ```python print(outputs.logits) @@ -303,7 +310,8 @@ tensor([[-1.5607, 1.6123], ``` {/if} -我们的模型预测第一句为 `[-1.5607, 1.6123]`,第二句为 `[4.1692, -3.3464]`。这些不是概率,而是 *logits*,即模型最后一层输出的原始、未经归一化的分数。为了将其转换为概率,它们需要经过[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)层(所有 🤗 Transformers 模型都输出 logits,因为用于训练的损失函数通常会将最后的激活函数(例如 SoftMax)与实际的损失函数(例如交叉熵)融合在一起。 +我们的模型预测第一句为 `[-1.5607, 1.6123]` ,第二句为 `[ 4.1692, -3.3464]` 。这些不是概率,而是 `logits(对数几率)` ,是模型最后一层输出的原始的、未标准化的分数。要转换为概率,它们需要经过 [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) 层(所有🤗Transformers 模型的输出都是 logits,因为训练时的损失函数通常会将最后的激活函数(如 SoftMax)与实际的损失函数(如交叉熵)融合): + {#if fw === 'pt'} ```py import torch @@ -333,12 +341,9 @@ tf.Tensor( ``` {/if} -现在我们可以看到,模型预测第一句为`[0.0402, 0.9598]`,第二句为`[0.9995, 0.0005]`。这些数字代表了模型预测每个类别(否定或肯定)的概率分值。 - -为了获得每个位置对应的标签,我们可以检查模型配置的`id2label`属性(下一节将对此进行详细介绍): -为了将这些概率分值转换为可识别的标签(“否定”或“肯定”),我们需要参考模型配置中的 id2label 属性。该属性将每个模型输出的 ID 映射到相应的标签。 - +现在我们可以看到,模型预测第一句的输出是 `[0.0402, 0.9598]` ,第二句 `[0.9995, 0.0005]` 。这些是可直接使用的概率分数。 +为了获得每个分数对应的标签,我们可以查看模型配置的 `id2label` 属性(下一节将对此进行详细介绍),该属性将每个模型输出的 ID 映射到相应的标签: ```python model.config.id2label @@ -348,15 +353,15 @@ model.config.id2label {0: 'NEGATIVE', 1: 'POSITIVE'} ``` -根据 id2label 属性,我们可以得出以下结论: +现在我们可以得出结论,模型预测如下: +- 第一句:消极的概率:0.0402,积极的概率:0.9598 +- 第二句:消极的概率:0.9995,积极的概率:0.0005 -- 第一句:否定:0.0402,肯定:0.9598 -- 第二句:否定:0.9995,肯定:0.0005 +我们已经成功地复刻了管道的三个步骤:使用 tokenizer 进行预处理、通过模型传递输入以及后处理!接下来,让我们花一些时间深入了解这些步骤中的每一步。 -至此,我们已经成功完成了文本分类任务的三个步骤:使用*tokenizer*对文本进行预处理;将预处理后的文本输入到模型中;对模型的输出结果进行后处理,并将预测结果转换为可识别的标签。接下来,我们将对这三个步骤进行更详细的解释。 -✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! +✏️ **试试看!** 选择两个(或更多)句子并分别在 `sentiment-analysis` 管道和自己实现的管道中运行它们。看一看是否获得的结果是不是相同的! diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 47a19592e..e47c88240 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -29,48 +29,50 @@ {/if} {#if fw === 'pt'} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,它极大程度的方便您从*checkpoint*实例化任何模型。 -AutoModel 类与其相关类本质上是对库中各种模型的简化包装。其智能之处在于能够自动识别检查点所对应的模型架构,并据此实例化相应的模型。 +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 `AutoModel` 类,当你希望从 checkpoint 实例化任何模型时,使用它非常方便。 + +`AutoModel` 类及其所有的相关类其实就是对库中可用的各种模型的简单包装。它是一个智能的包装,因为它可以自动猜测你的 checkpoint 适合的模型架构,然后实例化一个具有相同架构的模型。 {:else} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,它极大程度的方便您从*checkpoint*实例化任何模型。 -AutoModel 类与其相关类本质上是对库中各种模型的简化包装。其智能之处在于能够自动识别检查点所对应的模型架构,并据此实例化相应的模型。 +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 `TFAutoModel` 类,当你希望从 checkpoint 实例化任何模型时,这非常方便。 + +`TFAutoModel` 类及其所有的相关类其实就是对库中可用的各种模型的简单包装。它是一个智能的包装,因为它可以自动猜测你的 checkpoint 适合的模型架构,然后实例化一个具有相同架构的模型。 {/if} -如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 +然而,如果你知道要使用模型的类型,你可以直接使用其架构相对应的模型类。让我们看看如何使用 BERT 模型。 -## 创建转换器 [[创建转换器]] +## 创建 Transformer 模型 [[创建 Transformer 模型]] -初始化BERT模型需要做的第一件事是加载配置对象: +初始化 BERT 模型需要做的第一件事是加载 `Config` 对象: {#if fw === 'pt'} ```py from transformers import BertConfig, BertModel -# Building the config +# 初始化 Config 类 config = BertConfig() -# Building the model from the config +# 从 Config 类初始化模型 model = BertModel(config) ``` + {:else} + ```py from transformers import BertConfig, TFBertModel -# Building the config +# 初始化 Config 类 config = BertConfig() -# Building the model from the config +# 从 Config 类初始化模型 model = TFBertModel(config) ``` {/if} -配置包含许多用于构建模型的属性: +`config` 中包含许多用于构建模型的属性: ```py print(config) @@ -88,12 +90,11 @@ BertConfig { } ``` -虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:`hidden_size`属性定义了`hidden_states`向量的大小,`num_hidden_layers`定义了Transformer模型的层数。 - -### 不同的加载方式 [[不同的加载方式]] +虽然可能你还不知道这些属性的含义,但其中一部分应该比较眼熟: `hidden_size` 属性定义了 `hidden_states(隐状态)` 向量的大小,而 `num_hidden_layers` 定义了 Transformer 模型的层数。 -从默认配置创建模型会使用随机值对其进行初始化: +### 使用不同的加载方式 [[使用不同的加载方式]] +使用默认配置创建模型会使用随机值对其进行初始化: {#if fw === 'pt'} ```py @@ -102,7 +103,7 @@ from transformers import BertConfig, BertModel config = BertConfig() model = BertModel(config) -# Model is randomly initialized! +# 模型已随机初始化! ``` {:else} ```py @@ -111,18 +112,13 @@ from transformers import BertConfig, TFBertModel config = BertConfig() model = TFBertModel(config) -# Model is randomly initialized! +# 模型已随机初始化! ``` {/if} +这个模型是可以运行并得到结果的,但它会输出胡言乱语;它需要先进行训练才能正常使用。我们可以根据手头的任务从头开始训练模型,但正如你在 [第一章](/course/chapter1) 中看到的,这将需要很长的时间和大量的数据,并且产生的碳足迹会对环境产生不可忽视的影响。为了避免不必要的重复工作,能够共享和复用已经训练过的模型是非常重要的。 -该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练,我们可以根据手头的任务从头开始训练模型,但正如您在 -[Chapter 1](/course/chapter1) -所见,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 - - -加载已经训练过的Transformers模型很简单-我们可以使用`from_pretrained()` -方法: +加载已经训练过的 Transformers 模型很简单——我们可以使用 `from_pretrained()` 方法: {#if fw === 'pt'} ```py @@ -131,7 +127,7 @@ from transformers import BertModel model = BertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的`AutoModel`类替换`BertModel`类。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个*checkpoint*,那么它应该与另一个*checkpoint*无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如你在上一小节看到的,从现在开始,我们会将 `BertModel` 替换为等效的 `AutoModel` 类,这样可以摆脱对 checkpoint 的依赖;如果你的代码适用于一个 checkpoint 那么它就可以在另一个 checkpoint 无缝地工作。即使体系结构不同,这也适用,只要 checkpoint 是针对同类的任务(例如,情绪分析任务)训练的。 {:else} ```py @@ -140,37 +136,21 @@ from transformers import TFBertModel model = TFBertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的`AutoModel`替换`BertModel`类。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个*checkpoint*,那么它应该与另一个*checkpoint*无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如你在上一小节看到的,从现在开始,我们将用等效的 `TFAutoModel` 类替换 `TFBert` 模型。这样可以摆脱对 checkpoint 的依赖;如果你的代码适用于一个 checkpoint 那么它应该与另一个 checkpoint 无缝地工作。即使体系结构不同,这也适用,只要 checkpoint 是针对类似任务(例如,情绪分析任务)训练的。 {/if} -在上面的代码示例中,我们没有使用BertConfig,而是通过`bert-base-cased`标识符加载了一个预训练模型。这是一个模型检查点,由`BERT`的作者自己训练;您可以在 -[model card](https://huggingface.co/bert-base-cased)中找到更多细节. - - - -该模型现在使用*checkpoint*的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 - - +在上述代码示例中,我们没有使用 `BertConfig` ,而是通过 `bert-base-cased` 标签加载了一个预训练模型。这是一个由 BERT 的作者训练的模型 checkpoint 权重;你可以在其 [模型卡片](https://huggingface.co/bert-base-cased) 中查看更多详细信息。 -权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 -~/.cache/huggingface/transformers -. 您可以通过设置 -HF_HOME -环境变量来自定义缓存文件夹。 +现在,此模型已经用 checkpoint 的所有权重进行了初始化。它可以直接用于推理它训练过的任务,也可以在新任务上进行微调。通过使用预训练的权重进行训练,相比于从头开始训练,我们可以迅速获得比较好的结果。 +权重已下载并缓存在缓存文件夹中(因此,未来调用 `from_pretrained()` 方法的调用将不会重新下载它们)默认为 `~/.cache/huggingface/transformers` 。你可以通过设置 `HF_HOME` 环境变量来自定义缓存文件夹。 +加载模型的标识符可以是 Model Hub 上任何模型的标签,只要它与 BERT 架构兼容。可用的 BERT checkpoint 的完整列表可以在 [这里](https://huggingface.co/models?filter=bert) 找到。 -用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与`BERT`体系结构兼容。可以找到可用的`BERT`*checkpoint*的完整列表 -[here](https://huggingface.co/models?filter=bert) -. ### 保存模型 [[保存模型]] -保存模型和加载模型一样简单--我们使用 -save_pretrained() -方法,类似于 -from_pretrained() -方法: +保存模型和加载模型一样简单--我们使用 `save_pretrained()` 方法,该方法类似于 `from_pretrained()` 方法: ```py model.save_pretrained("directory_on_my_computer") @@ -192,33 +172,31 @@ config.json tf_model.h5 ``` {/if} -如果你看一下 -config.json -文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如*checkpoint*的来源以及上次保存检查点时使用的🤗 Transformers版本。 +如果你看一下 `config.json` 文件,你会认出构建模型架构所需的属性。这个文件还包含一些元数据,例如 checkpoint 的来源,以及你上次保存 checkpoint 时所使用的 🤗 Transformers 版本。 {#if fw === 'pt'} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件密切相关;这个配置是了解您的模型架构所必需的,而模型权重则是您模型的参数。 + +`pytorch_model.bin` 文件被称为 `state dictionary(状态字典)` ;它包含了你的模型的所有权重。这两个文件是相辅相成的;配置文件是构建你的模型架构所必需的,而模型权重就是你的模型参数。 {:else} -这个 *tf_model.h5* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件密切相关;这个配置是了解您的模型架构所必需的,而模型权重则是您模型的参数。 + +`tf_model.h5` 文件被称为 `state dictionary(状态字典)` ;它包含了你的模型的所有权重。这两个文件是相辅相成的;配置文件是构建你的模型架构所必需的,而模型权重就是你的模型参数。 {/if} -### 使用Transformers模型进行推理 [[使用Transformers模型进行推理]] +### 使用 Transformers 模型进行推理 [[使用 Transformers 模型进行推理]] -既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论*Tokenizer*之前,让我们先探讨模型接受哪些输入。 +既然你知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer 模型只能处理数字——由 tokenizer 转化后的数字。但在我们讨论 tokenizer 之前,让我们探讨一下模型可以接受的输入是什么。 -*Tokenizer*可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 +可以将输入转换为适当的框架张量,但为了帮助你了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 -假设我们有几个序列: +假设我们有几个句子: ```py sequences = ["Hello!", "Cool.", "Nice!"] ``` -*Tokenizer*将这些转换为词汇表索引,通常称为 -*input IDs* -. 每个序列现在都是一个数字列表!结果是: +tokenizer 将这些转换为词汇表索引,通常称为 `input IDs` 。每个句子现在都是一个数字列表!结果输出是: ```py no-format encoded_sequences = [ @@ -228,15 +206,18 @@ encoded_sequences = [ ] ``` -这是一个编码序列的列表:一个嵌套列表(列表的列表)。张量只接受矩形形状(可以想象成矩阵)。这个“数组”已经是矩形形状,因此将其转换为张量很容易: +这是一个编码序列列表:一个列表列表。张量只接受矩形(形状规则的的列表:每一列元素的数量都相同)。这个数组已经是矩形了,因此将其转换为张量很容易: {#if fw === 'pt'} + ```py import torch model_inputs = torch.tensor(encoded_sequences) ``` + {:else} + ```py import tensorflow as tf @@ -246,16 +227,10 @@ model_inputs = tf.constant(encoded_sequences) ### 使用张量作为模型的输入 [[使用张量作为模型的输入]] - - -在模型中使用张量非常简单-我们只需将输入称为模型: - +将张量输入给模型非常简单 —— 我们只需调用模型并输入: ```python output = model(model_inputs) ``` - - - -虽然模型接受许多不同的参数,但只有输入 ID 是必需的。 我们将在后面解释其他参数的作用以及何时需要它们,但首先我们需要仔细研究构建 Transformer 模型可以理解的输入的*tokenizer*。 \ No newline at end of file +虽然模型接受很多不同的参数,但只有 input IDs 是必需的。我们稍后会解释其他参数的作用以及何时需要它们,但首先我们需要仔细研究一下如何构建 Transformer 模型能理解的输入。 diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index f211b3db4..a5569f2aa 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -1,6 +1,6 @@ -# 标记器(Tokenizer) [[标记器(Tokenizer)]] +# Tokenizers [[Tokenizers]] {#if fw === 'pt'} @@ -24,30 +24,30 @@ -标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 +tokenizer 是 NLP 管道的核心组件之一。它们有一个非常明确的目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此 tokenizer 需要将我们的文本输入转换为数字。在本节中,我们将确切地探讨 tokenization 管道中发生的事情。 -在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 +在 NLP 任务中,通常处理的原始数据是文本。这里是一个例子: ``` Jim Henson was a puppeteer ``` -但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 +但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是 tokenizer 所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表达方式 —— 即对模型来说最有意义的方式 —— 如果可能,还要找到最简洁的表达方式。 -让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 +让我们看一下 tokenization 算法的一些示例,并尝试回答一些你可能对 tokenization 有的疑问。 -## 基于词的(Word-based) [[基于词的(Word-based)]] +## 基于单词(Word-based)的 tokenization [[基于单词(Word-based)的 tokenization ]] -想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: +想到的第一种 tokenizer 是基于词(word-based)的 tokenization。它通常很容易配置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示:
An example of word-based tokenization.
-有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: +有多种方法可以拆分文本。例如,我们可以通过使用 Python 的 `split()` 函数,使用空格将文本分割为单词: ```py tokenized_text = "Jim Henson was a puppeteer".split() @@ -58,72 +58,72 @@ print(tokenized_text) ['Jim', 'Henson', 'was', 'a', 'puppeteer'] ``` -还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 +此外,还有一些基于单词的 tokenizer 的变体,对标点符号有额外的规则。使用这类 tokenizer,我们最终可以得到一些非常大的“词汇表(vocabulary)”,其中词汇表的大小由我们在语料库中拥有的独立 tokens 的总数确定。 -每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 +每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。模型使用这些 ID 来识别每个词。 -如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 +如果我们想用基于单词的 tokenizer 完全覆盖一种语言,我们需要为语言中的每个单词设置一个标识符,这将生成大量的 tokens。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初也不会看到它们的相似性。 -最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 +最后,我们需要一个自定义 token 来表示不在我们词汇表中的单词。这被称为“unknown” token,通常表示为“[UNK]”或“<unk>”。如果你看到 tokenizer 产生了很多这样的 token 这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在转化过程中丢失信息。制作词汇表时的其中一个目标是 tokenizer 将尽可能少的单词标记为未知 tokens。 -减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 +减少未知 tokens 数量的一种方法是使用更深一层的 tokenizer 即基于字符(character-based)的 tokenizer -## 基于字符(Character-based) [[基于字符(Character-based)]] +## 基于字符(Character-based)的 tokenization [[基于字符(Character-based)的 tokenization ]] -基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: +基于字符的 tokenizer 将文本拆分为字符,而不是单词。这有两个主要好处: - 词汇量要小得多。 -- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 +- unknown tokens (out-of-vocabulary)要少得多,因为每个单词都可以由字符构建。 -但是这里也出现了一些关于空格和标点符号的问题: +但在此过程中也有一些问题,关于空格和标点符号:
An example of character-based tokenization.
-这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 +这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,但是单词则不然。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 -另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 +另一件要考虑的因素是,这样做会导致我们的模型需要处理大量的 tokens:虽然一个单词在基于单词的 tokenizer 中只是一个 token,但当它被转换为字符时,很可能就变成了 10 个或更多的 tokens -为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 +为了两全其美,我们可以使用结合这两种方法的第三种技术:基于子词(subword)的 tokenization。 -## 子词标记化 [[子词标记化]] +## 基于子词(subword)的 tokenization [[基于子词(subword)的 tokenization ]] -子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 +基于子词(subword)的 tokenization 算法依赖于这样一个原则:常用词不应被分解为更小的子词,但罕见词应被分解为有意义的子词。 -例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 +例如,“annoyingly”可能被视为一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词并且出现得更频繁,同时“annoyingly”的含义通过“annoying”和“ly”的复合含义得以保留。 -这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: +这里有一个例子,展示了基于子词的 tokenization 算法如何将序列“Let's do tokenization!”分词:
A subword tokenization algorithm.
-这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 +这些子词最终提供了大量的语义信息:例如,在上面的例子中,“tokenization”被分割成“token”和“ization”,这两个 tokens 在保持空间效率的同时具有语义意义(只需要两个 tokens 就能表示一个长词)。这让我们能够在词汇量小的情况下获得相对良好的覆盖率,并且几乎没有未知的 token。 -这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 +这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,你可以通过将子词串在一起来形成(几乎)任意长的复杂词。 -### 还有更多! [[还有更多!]] +### 还有更多![[还有更多!]] 不出所料,还有更多的技术。仅举几例: -- Byte-level BPE, 用于 GPT-2 -- WordPiece, 用于 BERT -- SentencePiece or Unigram, 用于多个多语言模型 +- Byte-level BPE,用于 GPT-2 +- WordPiece,用于 BERT +- SentencePiece or Unigram,用于多个多语言模型 -您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 +你现在应该对 tokenizer 的工作原理有足够的了解,可以开始使用 API 了。 ## 加载和保存 [[加载和保存]] -加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 +加载和保存 tokenizer 就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法会加载或保存分词器使用的算法(有点像模型的架构(architecture))以及其词汇表(有点像模型的权重(weights))。 -加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: +加载使用与 BERT 相同的 checkpoint 训练的 BERT tokenizer 与加载模型的方式相同,只是换成了 `Bert tokenizer` 类: ```py from transformers import BertTokenizer @@ -132,10 +132,12 @@ tokenizer = BertTokenizer.from_pretrained("bert-base-cased") ``` {#if fw === 'pt'} -如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +如同 `AutoModel` , `AutoTokenizer` 类将根据 checkpoint 名称在库中获取正确的 tokenizer 类,并且可以直接与任何 checkpoint 一起使用: {:else} -如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +如同 `TFAutoModel` , `AutoTokenizer` 类将根据 checkpoint 名称在库中获取正确的 tokenizer 类,并且可以直接与任何 checkpoint 一起使用: {/if} @@ -145,7 +147,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") ``` -我们现在可以使用标记器(tokenizer),如上一节所示: +现在我们可以像在上一节中显示的那样使用 tokenizer: ```python tokenizer("Using a Transformer network is simple") @@ -157,29 +159,29 @@ tokenizer("Using a Transformer network is simple") 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` -保存标记器(tokenizer)与保存模型相同: +保存 tokenizer 与保存模型完全相同: ```py tokenizer.save_pretrained("directory_on_my_computer") ``` -我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 +我们将在 [第三章](/Couse/chapter3) 中将更多地谈论 `token_type_ids` ,稍后我们将解释 `attention_mask` 。首先,让我们看看如何生成 `input_ids` 。为此,我们需要查看 tokenizer 的内部是如何实现的。 ## 编码 [[编码]] -将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 +将文本翻译成数字被称为编码(encoding)。编码分两步完成:分词,然后转换为 inputs ID。 -正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 +正如我们所见,第一步是将文本拆分为单词(或部分单词、标点符号等),通常称为 tokens 不同的不同的分词器使用的算法也不一样,这就是为什么我们需要使用模型名称来实例化 tokenizer,以确保我们使用模型预训练时使用的相同的算法。 -第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 +第二步是将这些 tokens 转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,tokenizer 有一个词汇表(vocabulary),这是我们在使用 `from_pretrained()` 方法实例化它时下载的部分。同样,我们需要使用与预训练模型时相同的词汇表。 -为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 +为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将单独执行部分 tokenization 管道的方法来向你展示这些步骤的中间结果,但在实践中,你应该直接在你的输入上调用 tokenizer(如第 2 小节所示)。 -### 标记化 [[标记化]] +### tokenization [[ tokenization ]] -标记化过程由标记器(tokenizer)的`tokenize()` 方法实现: +tokenization 过程由 tokenizer 的 `tokenize()` 方法实现: ```py from transformers import AutoTokenizer @@ -192,16 +194,17 @@ tokens = tokenizer.tokenize(sequence) print(tokens) ``` -此方法的输出是一个字符串列表或标记(token): +这个方法的输出是一个字符串列表,或者说 tokens ```python out ['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] ``` -这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 +这个 tokenizer 是一个基于子词的 tokenizer:它对词进行拆分,直到获得可以用其词汇表表示的 tokens。以 `transformer` 为例,它分为两个 tokens `transform` 和 `##er` 。 + +### 从 tokens 到 inputs ID [[从 tokens 到 inputs ID]] -### 从词符(token)到输入 ID [[从词符(token)到输入 ID]] -输入 ID 的转换由标记器(tokenizer)的`convert_tokens_to_ids()`方法实现: +inputs ID 的转换由 tokenizer 的 `convert_tokens_to_ids()` 方法实现: ```py ids = tokenizer.convert_tokens_to_ids(tokens) @@ -213,17 +216,17 @@ print(ids) [7993, 170, 11303, 1200, 2443, 1110, 3014] ``` -这些输出一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所见。 +这些输出,一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所示。 -✏️ **试试看!** 在我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! +✏️ **试试看!** 请将我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)执行最后两个步骤(分词和转换为 inputs ID)。检查你获得的 inputs ID 是否与我们在第二节中获得的一致! ## 解码 [[解码]] -*解码(Decoding)* 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 `decode()` 方法实现,如下: +解码(Decoding) 正好相反:从 inputs ID 到一个字符串。这以通过 `decode()` 方法实现: ```py decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) @@ -234,6 +237,6 @@ print(decoded_string) 'Using a Transformer network is simple' ``` -请注意, `decode` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 +请注意, `decode` 方法不仅将索引转换回 tokens,还将属于相同单词的 tokens 组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这样的功能将非常有用。 -到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 +到现在为止,你应该了解 tokenizer 可以处理的原子操作:分词、转换为 ID 以及将 ID 转换回字符串。然而,我们只是瞥到了冰山一角。在下一节中,我们将继续探讨它能力的极限,并看看如何克服它们。 diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index 576835144..26e529030 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -28,24 +28,21 @@ {/if} -在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: +在上一节中,我们探讨了最简单的案例:对一个较短的句子进行推理。然而,一些问题已经出现: -* 我们如何处理多个序列? +* 我们如何处理多个句子? +* 我们如何处理不同长度的多个句子? -* 我们如何处理多个序列不同长度? +* 词汇索引是唯一可以让模型运行的输入吗? +* 是否存在句子太长的问题? -* 词汇索引是让模型正常工作的唯一输入吗? - - -* 是否存在序列太长的问题? - -让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API解决它们 +让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API 解决它们 ## 模型需要一批输入 [[模型需要一批输入]] -在上一个练习中,您看到了序列如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: +在上一个练习中,你看到了句子如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: {#if fw === 'pt'} ```py @@ -61,7 +58,7 @@ sequence = "I've been waiting for a HuggingFace course my whole life." tokens = tokenizer.tokenize(sequence) ids = tokenizer.convert_tokens_to_ids(tokens) input_ids = torch.tensor(ids) -# This line will fail. +# 这一行会运行失败 model(input_ids) ``` @@ -82,7 +79,7 @@ sequence = "I've been waiting for a HuggingFace course my whole life." tokens = tokenizer.tokenize(sequence) ids = tokenizer.convert_tokens_to_ids(tokens) input_ids = tf.constant(ids) -# This line will fail. +# 这一行会运行失败 model(input_ids) ``` @@ -91,9 +88,9 @@ InvalidArgumentError: Input to reshape is a tensor with 14 values, but the reque ``` {/if} -哦,不!为什么失败了?“我们遵循了第2节中管道的步骤。 +哦,不!为什么失败了?“我们的确是按照第 2 节中管道的步骤一步步来做的。 -问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现tokenizer不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: +问题是我们向模型发送了一个单独的句子,而🤗 Transformers 模型默认情况下需要一个句子列表。在这里,当我们试图重现 tokenizer 在输入 `sequence` 后在其内部进行的所有操作。但如果你仔细观察,你会发现 tokenizer 不仅仅是将 inputs ID 的列表转换为张量,它还在其上添加了一个维度: {#if fw === 'pt'} ```py @@ -118,7 +115,7 @@ array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, ``` {/if} -让我们重试并添加一个新维度: +让我们再试一次并添加一个新的维度: {#if fw === 'pt'} ```py @@ -162,7 +159,7 @@ print("Logits:", output.logits) ``` {/if} -我们打印输入ID以及生成的logits-以下是输出: +让我们打印 inputs ID 以及生成的 logits 值,以下是输出: {#if fw === 'pt'} ```python out @@ -178,23 +175,24 @@ Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) ``` {/if} -*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: +批处理(Batching)是一次性通过模型发送多个句子的行为。如果你只有一句话,你可以构建一个只有一个句子的 batch: ``` batched_ids = [ids, ids] ``` -这是一批两个相同的序列! +这就是一个包含两个相同句子的 batch -✏️ **试试看!** 将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) +✏️ **试试看!** 将这个 `batched_ids` 列表转换为张量,并通过你的模型进行处理。检查你是否得到了与之前相同的 logits 值(但是重复了两次)! + -批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 +批处理支持模型在输入多个句子时工作。使用多个句子就像使用单个句子构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果你以前使用过张量,那么你知道它们必须是矩形,因此无法将 inputs ID 列表直接转换为张量。为了解决这个问题,我们通常填充输入(Padding)。 -## 填充输入 [[填充输入]] +## 填充输入(Padding) [[填充输入(Padding)]] 以下列表不能转换为张量: @@ -205,7 +203,7 @@ batched_ids = [ ] ``` -为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: +为了解决这个问题,我们将使用填充使张量成为标准的矩形。Padding 通过在值较少的句子中添加一个名为 `padding_id` 的特殊单词来确保我们所有的句子长度相同。例如,如果你有 10 个包含 10 个单词的句子和 1 个包含 20 个单词的句子,填充能确保所有句子都包含 20 个单词。在我们的示例中,填充后的张量如下所示: ```py no-format padding_id = 100 @@ -216,8 +214,7 @@ batched_ids = [ ] ``` -可以在tokenizer.pad_token_id中找到填充令牌ID. 让我们使用它,将我们的两句话分别发送到模型中,并分批发送到一起: - +我们可以在 `tokenizer.pad_token_id` 中找到填充 token 的 ID。让我们使用它,分别用模型处理这两个句子,然后再尝试将这两个句子放在一起用模型批处理: {#if fw === 'pt'} ```py no-format @@ -266,16 +263,15 @@ tf.Tensor( ``` {/if} -我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! - +咦,我们批处理预测中的 logits 值有点问题:第二行应该与第二句的 logits 相同,但我们得到了完全不同的值! -这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 +这是因为 Transformer 模型的关键特性:注意力层,它考虑了每个 token 的上下文信息。这具体来说,每个 token 的含义并非单独存在的,它的含义还取决于它在句子中的位置以及周围的其他 tokens。当我们使用填充(padding)来处理长度不同的句子时,我们会添加特殊的“填充 token”来使所有句子达到相同的长度。但是,注意力层会将这些填充 token 也纳入考虑,因为它们会关注序列中的所有 tokens。这就导致了一个问题:尽管填充 token 本身并没有实际的含义,但它们的存在会影响模型对句子的理解。我们需要告诉这些注意层忽略填充 token。这是通过使用注意力掩码(attention mask)层来实现的。 -## 注意力面具 [[注意力面具]] +## 注意力掩码(attention mask)层 [[注意力掩码(attention mask)层]] -*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 +注意力掩码(attention mask)是与 inputs ID 张量形状完全相同的张量,用 0 和 1 填充:1 表示应关注相应的 tokens,0 表示应忽略相应的 tokens(即,它们应被模型的注意力层忽视)。 -让我们用attention mask完成上一个示例: +让我们用 attention mask 修改上一个示例: {#if fw === 'pt'} ```py no-format @@ -320,35 +316,26 @@ tf.Tensor( ``` {/if} -现在我们得到了该批中第二个句子的相同登录。 +现在我们得到了批处理中第二句话相同的 logits 值。 -请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 +注意第二序列的最后一个值是填充 ID,其在注意力掩码中的值为 0。 -✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待Hugging Face课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! +✏️ **试试看!**在第二节使用的两个句子(“I've been waiting for a HuggingFace course my whole life.” 和 “I hate this so much!”)上手动进行 tokenize。将它们输入模型并检查你是否得到了与第二节相同的 logits 值。然后使用填充 token 将它们一起进行批处理,然后创建合适的注意力掩码。检查模型计算后是否得到了相同的结果! -## 长序列 [[长序列]] - -对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: - - - -* 使用支持的序列长度较长的模型。 - +## 更长的句子 [[更长的句子]] -* 截断序列。 +对于 Transformers 模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达 512 或 1024 个 的 tokens 序列,当使用模型处理更长的序列时,会崩溃。此问题有两种解决方案: +* 使用支持更长序列长度的模型。 +* 截断你的序列。 -模型有不同的支持序列长度,有些模型专门处理很长的序列。 -[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) -这是一个例子,另一个是 -[LED](https://huggingface.co/transformers/model_doc/led.html) -. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 +不同的模型支持的序列长度各不相同,有些模型专门用于处理非常长的序列。例如 [Longformer](https://huggingface.co/transformers/model_doc/longformer) 和 [LED](https://huggingface.co/transformers/model_doc/led) 。如果你正在处理需要非常长序列的任务,我们建议你考虑使用这些模型。 -否则,我们建议您通过指定max_sequence_length参数: +另外,我们建议你通过设定 `max_sequence_length` 参数来截断序列: ```py sequence = sequence[:max_sequence_length] diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index 17dd3c2c8..48b567d68 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -1,6 +1,6 @@ -# 把它们放在一起 [[把它们放在一起]] +# 综合应用 [[综合应用]] {#if fw === 'pt'} @@ -22,9 +22,9 @@ {/if} -在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 +在过去的几个章节中,我们已经尝试尽可能手动完成大部分工作。我们探索了 tokenizer 的运行机制,并且了解了分词、转换为 inputs ID、填充、截断以及注意力掩码的处理方式。 -然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 +然而,正如我们在第二节中看到的那样,🤗 Transformers API 能够通过一个高级函数为我们处理所有这些工作,接下来我们就要深入研究这个函数。当你直接在句子上调用你的 `tokenizer` 时,就可以得到转换后的可以直接放入模型的数据了: ```py from transformers import AutoTokenizer @@ -37,10 +37,9 @@ sequence = "I've been waiting for a HuggingFace course my whole life." model_inputs = tokenizer(sequence) ``` -这里,`model_inputs` -变量包含模型良好运行所需的一切。对于DistilBERT,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 +这里, `model_inputs` 变量包含模型运行所需的所有数据。在 DistilBERT 中,它包括 inputs ID 和注意力掩码(attention mask)。其他接受额外输入的模型也会有对应的 tokenizer 可以将输入转化为模型所需要的输入。 -正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: +正如我们将在下面的一些例子中看到的,这个函数非常强大。首先,它可以对单个句子进行处理: ```py sequence = "I've been waiting for a HuggingFace course my whole life." @@ -48,7 +47,7 @@ sequence = "I've been waiting for a HuggingFace course my whole life." model_inputs = tokenizer(sequence) ``` -它还一次处理多个序列,并且API没有任何变化: +它还一次处理多个多个,并且 API 的用法完全一致: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] @@ -56,51 +55,51 @@ sequences = ["I've been waiting for a HuggingFace course my whole life.", "So ha model_inputs = tokenizer(sequences) ``` -它可以根据几个目标进行填充: +它可以使用多种不同的方法对目标进行填充: ```py -# Will pad the sequences up to the maximum sequence length +# 将句子序列填充到最长句子的长度 model_inputs = tokenizer(sequences, padding="longest") -# Will pad the sequences up to the model max length +# 将句子序列填充到模型的最大长度 # (512 for BERT or DistilBERT) model_inputs = tokenizer(sequences, padding="max_length") -# Will pad the sequences up to the specified max length +# 将句子序列填充到指定的最大长度 model_inputs = tokenizer(sequences, padding="max_length", max_length=8) ``` -它还可以截断序列: +它还可以对序列进行截断: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] -# Will truncate the sequences that are longer than the model max length +# 将截断比模型最大长度长的句子序列 # (512 for BERT or DistilBERT) model_inputs = tokenizer(sequences, truncation=True) -# Will truncate the sequences that are longer than the specified max length +# 将截断长于指定最大长度的句子序列 model_inputs = tokenizer(sequences, max_length=8, truncation=True) ``` -标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: +`tokenizer` 对象可以处理指定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们告诉 tokenizer 返回不同框架的张量 —— `"pt"` 返回 PyTorch 张量, `"tf"` 返回 TensorFlow 张量,而 `"np"` 则返回 NumPy 数组: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] -# Returns PyTorch tensors +# 返回 PyTorch tensors model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") -# Returns TensorFlow tensors +# 返回 TensorFlow tensors model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") -# Returns NumPy arrays +# 返回 NumPy arrays model_inputs = tokenizer(sequences, padding=True, return_tensors="np") ``` -## 特殊词符(token) [[特殊词符(token)]] +## 特殊的 tokens [[特殊的 tokens ]] -如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: +如果我们看一下 tokenizer 返回的 inputs ID,我们会发现它们与之前的略有不同: ```py sequence = "I've been waiting for a HuggingFace course my whole life." @@ -118,7 +117,7 @@ print(ids) [1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] ``` -一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: +句子的开始和结束分别增加了一个 inputs ID。我们来解码上述的两个 ID 序列,看看是怎么回事: ```py print(tokenizer.decode(model_inputs["input_ids"])) @@ -130,11 +129,11 @@ print(tokenizer.decode(ids)) "i've been waiting for a huggingface course my whole life." ``` -标记器在开头添加了特殊单词`[CLS]`,在结尾添加了特殊单词`[SEP]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 + tokenizer 在开头添加了特殊单词 `[CLS]` ,在结尾添加了特殊单词 `[SEP]` 。这是因为模型在预训练时使用了这些字词,所以为了得到相同的推断结果,我们也需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的特殊单词;模型也可能只在开头或结尾添加这些特殊单词。无论如何,tokenizer 知道哪些是必需的,并会为你处理这些问题。 -## 结束:从标记器到模型 [[结束:从标记器到模型]] +## 小结:从 tokenizer 到模型 [[小结:从 tokenizer 到模型]] -现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要API: +现在我们已经看到 `tokenizer` 对象在处理文本时的所有步骤,让我们最后再看一次它如何通过 tokenizer API 处理多个序列(填充!),非常长的序列(截断!)以及多种类型的张量: {#if fw === 'pt'} ```py diff --git a/chapters/zh-CN/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx index b3924309b..b1cf2de2d 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,32 +1,24 @@ -# 基本用法完成! [[基本用法完成!]] +# 基本用法完成![[基本用法完成!]] -很好地完成了到这里的课程!总而言之,在本章中,您可以: +恭喜你跟随课程走到这里!回顾一下,在这一章中,你已经: -- 学习了Transformers模型的基本构造块。 +- 学习了 Transformers 模型的基本构造块。 +- 了解了 Tokenizer 管道的组成。 -- 了解了标记化管道的组成。 +- 了解了如何在实践中使用 Transformers 模型。 +- 学习了如何利用 tokenizer 将文本转换为模型可以理解的张量。 -- 了解了如何在实践中使用Transformers模型。 +- 设定了 tokenizer 和模型,可以从输入的文本获取预测的结果。 +- 了解了 inputs IDs 的局限性,并学习了关于注意力掩码(attention mask)的知识。 -- 学习了如何利用分词器将文本转换为模型可以理解的张量。 +- 试用了灵活且可配置的 Tokenizer 方法。 - -- 将分词器和模型一起设置,以从文本到预测。 - - -- 了解了inputs IDs的局限性,并了解了attention mask。 - - -- 使用多功能和可配置的分词器方法。 - - - -从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 +从现在开始,你应该能够自由浏览🤗 Transformers 文档:你会遇到许多看起来很熟悉的词汇;而且到目前为止,你已经见到了你大部分时间会使用的方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/8.mdx b/chapters/zh-CN/chapter2/8.mdx index ff8f98b8c..26440c09e 100644 --- a/chapters/zh-CN/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -9,68 +9,68 @@ classNames="absolute z-10 right-0 top-0" /> -### 1. 语言建模 Pipeline 的顺序是什么? +### 1. 自然语言处理流程的顺序是什么? -### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? +### 2. Transformer 模型的输出有的张量多少个维度,每个维度分别是什么? -### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? +### 3.下列哪一个是子词分词的例子(从分词的颗粒度来划分)? -### 4.什么是模型的Head层? +### 4.什么是模型头(Haed 层)? {#if fw === 'pt'} -### 5.什么是AutoModel? +### 5.什么是 AutoModel? AutoTrain 产品相混淆了?" + text: "根据你的数据自动进行训练的模型", + explain: "错误。你可能把 AutoModel 与 Hugging Face 的AutoTrain 产品相混淆了?" }, { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + text: "一个根据 checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说:AutoModel只需要知道初始化的 checkpoint(检查点)名称就可以返回正确的体系结构。", correct: true }, { text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + explain: "不正确;虽然有些 checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择 checkpoint(检查点)。你应该前往 Model Hub 寻找完成所需任务的最佳 checkpoint(检查点)!" } ]} /> {:else} -### 5.什么是 TFAutoModel? +### 5.什么是 TFAutoModel? AutoTrain 产品相混淆了?" + text: "根据你的数据自动进行训练的模型", + explain: "错误。你可能把 TFAutoModel 与 Hugging Face 的AutoTrain 产品相混淆了?" }, { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + text: "一个根据 checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说:TFAutoModel只需要知道初始化的 checkpoint(检查点)名称就可以返回正确的体系结构。", correct: true }, { text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + explain: "不正确;虽然有些 checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择 checkpoint(检查点)。你应该前往 Model Hub 寻找完成所需任务的最佳 checkpoint(检查点)!" } ]} /> {/if} -### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? +### 6.当将不同长度的句子序列在一起批处理时,需要进行哪些处理? -### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? +### 7.使用 SoftMax 激活函数对序列分类(Sequence Classification)模型的 logits 输出进行处理有什么意义? -### 8.大多数标记器(Tokenizer)的API以什么方法为核心? +### 8.Tokenizer API 的核心方法是哪一个? 编码 ,因为它可以将文本编码为id,将预测的id解码为文本", - explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" + text: "encode 因为它可以将文本编码为 ID,将预测的 ID 解码为文本", + explain: "错!虽然 encode 方法确实是 Tokenizer 中的方法之一,但是它并不是核心的方法,此外将预测 ID 解码为文本的是 decode。" }, { - text: "直接调用标记器(Tokenizer)对象。", - explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", + text: "直接调用 Tokenizer 对象。", + explain: "完全正确! tokenizer (Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它同时也可以从模型中获取预测。", correct: true }, { text: "pad(填充)", - explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" + explain: "错!pad(填充)非常有用,但它只是 Tokenizer API 的一部分。" }, { - text: "tokenize(标记)", - explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) API的核心方法。" + text: "tokenize", + explain: "可以说,tokenize方法是最有用的方法之一,但它不是 Tokenizer API 的核心方法。" } ]} /> -### 9.这个代码示例中的`result`变量包含什么? +### 9.这个代码示例中的 `result` 变量包含什么? ```py from transformers import AutoTokenizer @@ -220,23 +220,23 @@ result = tokenizer.tokenize("Hello!") __call__ 或 convert_tokens_to_ids方法的作用!" + text: "一个 ID 的列表", + explain: "不正确;这是 __call__convert_tokens_to_ids方法的作用!" }, { - text: "包含所有标记(Token)的字符串", - explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" + text: "包含所有分词后的的字符串", + explain: "这将是次优的答案,因为 tokenize 方法会将字符串拆分为多个 tokens 的列表。" } ]} /> {#if fw === 'pt'} -### 10.下面的代码有什么错误吗? +### 10.下面的代码有什么错误吗? ```py from transformers import AutoTokenizer, AutoModel @@ -251,22 +251,22 @@ result = model(**encoded) choices={[ { text: "不,看起来是对的。", - explain: "不幸的是,将一个模型与一个用不同Checkpoint(检查点)训练的Tokenizer(标记器)耦合在并不是一个好主意。模型没有在这个这个Tokenizer(标记器)上训练来理解Tokenizer(标记器)的输出,因此模型输出(如果它可以运行的话)不会有任何意义。" + explain: "将一个模型与一个在不同 checkpoint 训练的 tokenizer 耦合在一起并不是一个好主意。模型没有在这个这个 tokenizer 上训练来理解 tokenizer 的输出,因此模型的输出(如果它可以运行的话)会是错乱的。" }, { - text: "Tokenizer(标记器)和模型应该来自相同的Checkpoint(检查点)。", + text: "Tokenizer 和模型应该来自相同的 checkpoint。", explain: "对!", correct: true }, { - text: "由于每个输入都是一个Batch,因此可以使用标记器(Tokenizer)对其进行平移和截断来改善这段代码。", - explain: "的确,每个模型都需要Batch类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" + text: "由于模型输入需要是一个 Batch,因此可以使用 tokenizer 对其进行截断或填充来改进这段代码。", + explain: "的确,模型输入需要是一个 Batch。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而截断或填充这些技术是用来批处理一个句子列表使其长度一致的。" } ]} /> {:else} -### 10.下面的代码有什么错误吗? +### 10.下面的代码有什么错误吗? ```py from transformers import AutoTokenizer, TFAutoModel @@ -281,16 +281,16 @@ result = model(**encoded) choices={[ { text: "不,看起来是对的。", - explain: "不幸的是,将一个模型与一个用不同Checkpoint(检查点)训练的Tokenizer(标记器)耦合在并不是一个好主意。模型没有在这个这个Tokenizer(标记器)上训练来理解Tokenizer(标记器)的输出,因此模型输出(如果它可以运行的话)不会有任何意义。" + explain: "不幸的是,将一个模型与一个不同 checkpoint 训练的 tokenizer 耦合在并不是一个好主意。模型没有在这个这个 tokenizer 上训练来理解 tokenizer 的输出,因此模型的输出(如果它可以运行的话)会是错乱的。" }, { - text: "Tokenizer(标记器)和模型应该来自相同的Checkpoint(检查点)。", + text: "Tokenizer 和模型应该来自相同的 checkpoint。", explain: "对!", correct: true }, { - text: "由于每个输入都是一个Batch,因此可以使用标记器(Tokenizer)对其进行平移和截断来改善这段代码。", - explain: "的确,每个模型都需要Batch类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" + text: "由于每个输入都是一个 Batch,因此可以使用 tokenizer 对其进行平移和截断来改善这段代码。", + explain: "的确,每个模型都需要 Batch 类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" } ]} /> diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index 2e1c920e1..13e70ddd9 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -7,20 +7,22 @@ classNames="absolute z-10 right-0 top-0" /> -在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: +在 [第二章](/course/chapter2) 我们探索了如何使用 Tokenizer 和预训练模型进行预测。那么如何使用自己的数据集微调预训练模型呢?本章将解决这个问题!你将学到: {#if fw === 'pt'} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用高级`训练`API微调一个模型 + +* 如何从模型中心(hub)加载大型数据集 +* 如何使用高级的 `Trainer` API 微调一个模型 * 如何使用自定义训练过程 -* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 +* 如何利用🤗 Accelerate 库在所有分布式设备上轻松运行自定义训练过程 {:else} -* 如何从模型中心(hub)准备大型数据集 + +* 如何从模型中心(hub)加载大型数据集 * 如何使用 Keras 微调模型 * 如何使用 Keras 进行预测 * 如何使用自定义指标 {/if} -为了将经过训练的参数上传到Hugging Face Hub,您需要一个huggingface.co帐户: [创建一个账户](https://huggingface.co/join) \ No newline at end of file +为了将经过训练的参数上传到 Hugging Face Hub,你需要一个 huggingface.co 帐户: [创建一个账户](https://huggingface.co/join) diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 03f5e61c0..2591001fd 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -23,13 +23,14 @@ {/if} {#if fw === 'pt'} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: + +在这一小节你将学习 [第一小节](/course/chapter2) 中提到的“如何使用模型中心(hub)加载大型数据集”,下面是用模型中心的数据在 PyTorch 上训练句子分类器的一个例子: ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification -# Same as before +# 和之前一样 checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) @@ -39,7 +40,7 @@ sequences = [ ] batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -# This is new +# 新增部分 batch["labels"] = torch.tensor([1, 1]) optimizer = AdamW(model.parameters()) @@ -47,15 +48,17 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` + {:else} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: + +在这一小节你将学习 [第一小节](/course/chapter2) 中提到的“如何使用模型中心(hub)加载大型数据集”,下面是用模型中心的数据在 TensorFlow 上训练句子分类器的一个例子: ```python import tensorflow as tf import numpy as np from transformers import AutoTokenizer, TFAutoModelForSequenceClassification -# Same as before +# 和之前一样 checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) @@ -65,18 +68,19 @@ sequences = [ ] batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) -# This is new +# 新增部分 model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` + {/if} -当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 +然而仅用两句话训练模型不会产生很好的效果,你需要准备一个更大的数据集才能得到更好的训练结果。 -在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 +在本节中,我们以 MRPC(微软研究院释义语料库)数据集为例,该数据集由威廉·多兰和克里斯·布罗克特在 [这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf) 发布,由 5801 对句子组成,每个句子对带有一个标签来指示它们是否为同义(即两个句子的意思相同)。在本章选择该数据集的原因是它的数据体量小,容易对其进行训练。 -### 从模型中心(Hub)加载数据集 [[从模型中心(Hub)加载数据集]] +## 从模型中心(Hub)加载数据集 [[从模型中心(Hub)加载数据集]] {#if fw === 'pt'} @@ -84,9 +88,14 @@ model.train_on_batch(batch, labels) {/if} -模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 +模型中心(hub)不仅仅包含模型,还有许多别的语言的数据集。访问 [Datasets](https://huggingface.co/datasets) 的链接即可进行浏览。我们建议你在完成本节的学习后阅读一下 [加载和处理新的数据集](https://huggingface.co/docs/datasets/loading) 这篇文章,这会让你对 huggingface 的数据集理解更加清晰。现在让我们使用 MRPC 数据集中的 [GLUE 基准测试数据集](https://gluebenchmark.com) 作为我们训练所使用的数据集,它是构成 MRPC 数据集的 10 个数据集之一,作为一个用于衡量机器学习模型在 10 个不同文本分类任务中性能的学术基准。 + +🤗 Datasets 库提供了一条非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。你可以以下代码下载 MRPC 数据集: + + +⚠️ **警告** 确保你已经运行 `pip install datasets` 安装了 `datasets`。然后,再继续下面的加载 MRPC 数据集和打印出来查看其内容。 -🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: + ```py from datasets import load_dataset @@ -95,7 +104,7 @@ raw_datasets = load_dataset("glue", "mrpc") raw_datasets ``` -```python out +```python DatasetDict({ train: Dataset({ features: ['sentence1', 'sentence2', 'label', 'idx'], @@ -112,46 +121,46 @@ DatasetDict({ }) ``` -正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 +现在我们获得了一个 `DatasetDict` 对象,这个对象包含训练集、验证集和测试集。每一个集合都包含 4 个列( `sentence1` , `sentence2` , `label` 和 `idx` )以及一个代表行数的变量(每个集合中的行的个数)。运行结果显示该训练集中有 3668 对句子,验证集中有 408 对,测试集中有 1725 对。 -默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/datasets**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 +默认情况下,该命令会下载数据集并缓存到 `~/.cache/huggingface/datasets` 。回想在第 2 章中我们学到过,可以通过设置 `HF_HOME` 环境变量来自定义缓存的文件夹。 -我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: +我们可以访问该数据集中的每一个 `raw_train_dataset` 对象,例如使用字典: ```py raw_train_dataset = raw_datasets["train"] raw_train_dataset[0] ``` -```python out +```python {'idx': 0, 'label': 1, 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} ``` -我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: +现在可以看到标签已经是整数了,因此不需要对标签做任何预处理。如果想要知道不同数字对应标签的实际含义,我们可以查看 `raw_train_dataset` 的 `features` 。这告诉我们每列的类型: ```py raw_train_dataset.features ``` -```python out +```python {'sentence1': Value(dtype='string', id=None), 'sentence2': Value(dtype='string', id=None), 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), 'idx': Value(dtype='int32', id=None)} ``` -在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 +上面的例子中的 `Label(标签)` 是一种 `ClassLabel(分类标签)` ,也就是使用整数建立起类别标签的映射关系。 `0` 对应于 `not_equivalent(非同义)` , `1` 对应于 `equivalent(同义)` 。 -✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? +✏️ **试试看!** 查看训练集的第 15 行元素和验证集的 87 行元素。他们的标签是什么? -### 预处理数据集 [[预处理数据集]] +## 预处理数据集 [[预处理数据集]] {#if fw === 'pt'} @@ -159,7 +168,7 @@ raw_train_dataset.features {/if} -为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 +为了预处理数据集,我们需要将文本转换为模型能够理解的数字。在 [第二章](/course/chapter2) 我们已经学习过。这是通过一个 Tokenizer 完成的,我们可以向 Tokenizer 输入一个句子或一个句子列表。以下代码表示对每对句子中的所有第一句和所有第二句进行 tokenize: ```py from transformers import AutoTokenizer @@ -170,14 +179,14 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` -然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: +不过在将两句话传递给模型,预测这两句话是否是同义之前,我们需要给这两句话依次进行适当的预处理。Tokenizer 不仅仅可以输入单个句子,还可以输入一组句子,并按照 BERT 模型所需要的输入进行处理: ```py inputs = tokenizer("This is the first sentence.", "This is the second one.") inputs ``` -```python out +```python { 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], @@ -185,44 +194,45 @@ inputs } ``` -我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 +我们在 [第二章](/course/chapter2) 讨论了 `输入词id(input_ids)` 和 `注意力遮罩(attention_mask)` ,但尚未讨论 `token类型ID(token_type_ids)` 。在本例中, `token类型ID(token_type_ids)` 的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 -✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? +✏️ ** 试试看!** 选取训练集中的第 15 个元素,将两句话分别进行tokenization。结果和上方的例子有什么不同? -如果我们将**input_ids**中的id转换回文字: +如果将 `input_ids` 中的 id 转换回文字: ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` -我们将得到: +将得到: -```python out +```python ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: +所以我们看到模型需要输入的形式是 `[CLS] sentence1 [SEP] sentence2 [SEP]` 。所以当有两句话的时候, `token类型ID(token_type_ids)` 的值是: -```python out +```python ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. +现在输入中 `[CLS] sentence1 [SEP]` 它们的 `token_type_ids` 均为 `0` ,而其他部分例如 `sentence2 [SEP]` ,所有的 `token_type_ids` 均为 `1` 。 -请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 +请注意,如果选择其他的 checkpoint,不一定具有 `token_type_ids` ,比如,DistilBERT 模型就不会返回。只有当 tokenizer 在预训练期间使用过这一层,也就是模型在构建时需要它们时,才会返回 `token_type_ids` 。 -用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 +在这里,BERT 使用了带有 `token_type_ids` 的预训练 tokenizer,除了我们在 [第一章](/course/chapter1) 中讨论的掩码语言建模,还有一个额外的应用类型称为“下一句预测”。这个任务的目标是对句子对之间的关系进行建模。 -在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 -一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 +在下一句预测任务中,会给模型输入成对的句子(带有随机遮罩的 token),并要求预测第二个句子是否紧跟第一个句子。为了使任务具有挑战性,提高模型的泛化能力,数据集中一有一半句子对中的句子在原始文档中顺序排列,另一半句子对中的两个句子来自两个不同的文档。 -现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: +一般来说无需要担心在你的输入中是否需要有 `token_type_ids` 。只要你使用相同的 checkpoint 的 Tokenizer 和模型,Tokenizer 就会知道向模型提供什么,一切都会顺利进行。 + +现在我们已经了解了 Tokenizer 如何处理一对句子,我们可以用它来处理整个数据集:就像在 [第二章](/course/chapter2) 中一样,我们可以给 Tokenizer 提供一对句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在 [第二章](/course/chapter2) 中看到的填充和截断选项兼容。因此预处理训练数据集的一种方法是: ```py tokenized_dataset = tokenizer( @@ -233,29 +243,31 @@ tokenized_dataset = tokenizer( ) ``` -这很有效,但它的缺点是返回字典(字典的键是**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)**,字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以[Apache Arrow](https://arrow.apache.org/)文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。 +这种方法虽然有效,但有一个缺点是它返回的是一个字典(字典的键是 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token类型ID(token_type_ids)` ,字典的值是键所对应值的列表)。这意味着在转换过程中要有足够的内存来存储整个数据集才不会出错。不过来自🤗 Datasets 库中的数据集是以 [Apache Arrow](https://arrow.apache.org) 格式存储在磁盘上的,因此你只需将接下来要用的数据加载在内存中,而不是加载整个数据集,这对内存容量的需求比较友好。 -为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: +我们将使用 [Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) 方法将数据保存为 dataset 格式,如果我们需要做更多的预处理而不仅仅是 tokenization 它还支持了一些额外的自定义的方法。 `map()` 方法的工作原理是使用一个函数处理数据集的每个元素。让我们定义一个对输入进行 tokenize 的函数: ```py def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 +该函数接收一个字典(与 dataset 的项类似)并返回一个包含 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token_type_ids` 键的新字典。请注意,如果 `example` 字典所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以运行,就像前面的例子一样, `tokenizer` 可以处理成对的句子列表,这样的话我们可以在调用 `map()` 时使用该选项 `batched=True` ,这将显著加快处理的速度。 `tokenizer` 来自 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库,由 Rust 编写而成。当一次给它很多输入时,这个 `tokenizer` 可以处理地非常快。 + +请注意,我们暂时在 `tokenize_function` 中省略了 padding 参数。这是因为将所有的样本填充到最大长度有些浪费。一个更好的做法是:在构建 batch 的时候。这样我们只需要填充到每个 batch 中的最大长度,而不是整个数据集的最大长度。当输入长度不稳定时,这可以节省大量时间和处理能力! -请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! +下面是我们如何使用一次性 `tokenize_function` 处理整个数据集。我们在调用 `map` 时使用了 `batch =True` ,这样函数就可以同时处理数据集的多个元素,而不是分别处理每个元素,这样可以更快进行预处理。 -下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 +以下是如何使用 tokenization 函数处理我们的整个数据集的方法。我们在调用 map 时使用了 `batched=True` ,因此该函数会一次性处理数据集的多个元素,而不是单独处理每个元素。这样可以实现更快的预处理。 ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: +🤗Datasets 库进行这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: -```python out +```python DatasetDict({ train: Dataset({ features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], @@ -272,42 +284,47 @@ DatasetDict({ }) ``` -在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 +在使用预处理函数 `map()` 时,甚至可以通过传递 `num_proc` 参数并行处理。我们在这里没有这样做,因为在这个例子中🤗 Tokenizers 库已经使用多线程来更快地对样本 tokenize,但是如果没有使用该库支持的快速 tokenizer,使用 `num_proc` 可能会加快预处理。 -我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 +我们的 `tokenize_function` 返回包含 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token_type_ids` 键的字典,这三个字段被添加到数据集的三个集合里(训练集、验证集和测试集)。请注意,如果预处理函数 `map()` 为现有键返回一个新值,我们可以通过使用 `map()` 函数返回的新值修改现有的字段。 -最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 +我们最后需要做的是将所有示例填充到该 batch 中最长元素的长度,这种技术被称为动态填充。 -### 动态填充 [[动态填充]] +## 动态填充 [[动态填充]] {#if fw === 'pt'} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +负责在批处理中将数据整理为一个 batch 的函数称为 `collate 函数` 。这是一个可以在构建 `DataLoader` 时传递的一个参数,默认是一个将你的数据集转换为 PyTorch 张量并将它们拼接起来的函数(如果你的元素是列表、元组或字典,则会使用递归进行拼接)。这在本例子中下是不可行的,因为我们的输入的大小可能是不相同的。我们特意推迟了填充的时间,只在每个 batch 上进行必要的填充,以避免出现有大量填充的过长输入。这将大大加快训练速度,但请注意,如果你在 TPU 上训练,需要注意一个问题——TPU 喜欢固定的形状,即使这需要额外填充很多无用的 token。 {:else} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 +负责在批处理中将数据整理为一个 batch 的函数称为 `collate 函数` 。默认的拼合函数只会将你的样本转换为 `tf.Tensor` 并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归进行拼接)。这在本例中是不可行的,因为我们的输入不是都是相同大小的。我们特意推迟了填充时间,只在每个 batch 上进行填充,以避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在 TPU 上训练,需要注意一个问题——TPU 喜欢固定的形状,即使这需要额外填充很多无用的 token。 {/if} -为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: +为了解决句子长度不统一的问题,我们必须定义一个 collate 函数,该函数会将每个 batch 句子填充到正确的长度。幸运的是,🤗transformer 库通过 `DataCollatorWithPadding` 为我们提供了这样一个函数。当你实例化它时,它需要一个 tokenizer (用来知道使用哪种填充 token 以及模型期望在输入的左边填充还是右边填充),然后它会自动完成所有需要的操作: {#if fw === 'pt'} + ```py from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` + {:else} + ```py from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") ``` + {/if} -为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: +为了测试这个新东西,让我们从我们的训练集中抽取几个样本,在这里,我们删除列 `idx` , `sentence1` 和 `sentence2` ,因为不需要它们,而且删除包含字符串的列(我们不能用字符串创建张量),然后查看一个 batch 中每个条目的长度: ```py samples = tokenized_datasets["train"][:8] @@ -315,11 +332,13 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se [len(x) for x in samples["input_ids"]] ``` -```python out +```python [50, 59, 47, 67, 59, 50, 62, 32] ``` -毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: +不出所料,我们得到了不同长度的样本,从 32 到 67。动态填充意味着这个 batch 都应该填充到长度为 67,这是这个 batch 中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查 `data_collator` 是否正确地动态填充了这批样本: + +```py: ```py batch = data_collator(samples) @@ -328,7 +347,7 @@ batch = data_collator(samples) {#if fw === 'tf'} -```python out +```python {'attention_mask': TensorShape([8, 67]), 'input_ids': TensorShape([8, 67]), 'token_type_ids': TensorShape([8, 67]), @@ -337,26 +356,26 @@ batch = data_collator(samples) {:else} -```python out +```python {'attention_mask': torch.Size([8, 67]), 'input_ids': torch.Size([8, 67]), 'token_type_ids': torch.Size([8, 67]), 'labels': torch.Size([8])} ``` -看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! +看起来不错!现在,我们已经从原始文本转化为了模型可以处理的数据,我们准备好对其进行微调。 {/if} -✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 +✏️ ** 试试看!** 在 GLUE SST-2 数据集上复刻上述预处理。它有点不同,因为它是由单句而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个进阶的挑战是尝试编写一个可用于任何 GLUE 任务的预处理函数。 {#if fw === 'tf'} -现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! +现在我们有了 dataset 和 data collator,我们需要使用 data collator 批量地处理 dataset。我们可以手动加载批次并进行整合,但这需要大量工作,性能也有可能不好。相反,有一个简单的方法为这个问题提供高效的解决方案: `to_tf_dataset()` 。它将把你的数据集包装一个 `tf.data.Dataset` 类中,这个方法带有一个可选的 data collator 功能。 `tf.data.Dataset` 是 TensorFlow 的本地格式,Keras 可以直接用它来进行 `model.fit()` ,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。让我们用我们的数据集演示一下这个方法! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -376,6 +395,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 +就是这样!我们可以把这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单和愉快。 {/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index efcdb0916..b67557bc5 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -11,9 +11,9 @@ -🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](https://colab.research.google.com/). +🤗 Transformers 提供了一个 `Trainer` 类,可以帮助你在数据集上微调任何预训练模型。在上一节中完成所有数据预处理工作后,你只需完成几个步骤来定义 `Trainer` 。最困难的部分可能是准备运行 `Trainer.train()` 所需的环境,因为在 CPU 上运行速度非常慢。如果你没有设置 GPU,可以使用 [Google Colab](https://colab.research.google.com/) (国内网络无法使用) 上获得免费的 GPU 或 TPU。 -下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码: +下面的示例假设你已经执行了上一节中的示例。下面是在开始学习这一节之前你需要运行的代码: ```py from datasets import load_dataset @@ -34,7 +34,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ### Training [[Training]] -在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 +在我们定义 `Trainer` 之前第一步要定义一个 `TrainingArguments` 类,它包含 `Trainer` 在训练和评估中使用的所有超参数。你只需要提供的参数是一个用于保存训练后的模型以及训练过程中的 checkpoint 的目录。对于其余的参数你可以保留默认值,这对于简单的微调应该效果就很好了。 ```py from transformers import TrainingArguments @@ -44,11 +44,11 @@ training_args = TrainingArguments("test-trainer") -💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 +💡 如果你想在训练期间自动将模型上传到 Hub,请将 `push_to_hub=True` 添加到 TrainingArguments 之中。我们将在 [第四章](/course/chapter4/3) 中详细介绍这部分。 -第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: +第二步是定义我们的模型。与 [前一章](/course/chapter2) 一样,我们将使用 `AutoModelForSequenceClassification` 类,它有两个参数: ```py from transformers import AutoModelForSequenceClassification @@ -56,9 +56,9 @@ from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 +你会注意到,和 [第二章](/course/chapter2) 不同的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的 head 已经被丢弃,而是添加了一个适合句子序列分类的新头部。这些警告表明一些权重没有使用(对应于被放弃的预训练头的权重),而有些权重被随机初始化(对应于新 head 的权重)。 -一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**data_collator** ,和 **tokenizer** : +一旦有了我们的模型,我们就可以定义一个 `Trainer` 把到目前为止构建的所有对象 —— `model` ,`training_args`,训练和验证数据集, `data_collator` 和 `tokenizer` 传递给 `Trainer` : ```py from transformers import Trainer @@ -73,23 +73,23 @@ trainer = Trainer( ) ``` -请注意,当您在这里完成**tokenizer**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! +请注意,当你在这里传递 `tokenizer` 时, `Trainer` 默认使用的 `data_collator` 是之前预定义的 `DataCollatorWithPadding`。所以你可以在本例中可以跳过 `data_collator=data_collator` 一行。在第 2 节中向你展示这部分处理过程仍然很重要! -为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : +要在我们的数据集上微调模型,我们只需调用 `Trainer` 的 `train()` 方法: ```py trainer.train() ``` -这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: +开始微调(在 GPU 上应该需要几分钟),每 500 步报告一次训练损失。然而它不会告诉你模型的性能(或质量)如何。这是因为: -1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 -2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 +1. 我们没有告诉 Trainer 在训练过程中进行评估,比如将 `evaluation_strategy` 设置为“ `step` ”(在每个 `eval_steps` 步骤评估一次)或“ `epoch` ”(在每个 epoch 结束时评估)。 +2. 我们没有为 `Trainer` 提供一个 `compute_metrics()` 函数来计算上述评估过程的指标(否则评估将只会输出 loss,但这不是一个非常直观的数字)。 ### 评估 [[评估]] -让我们看看如何构建一个有用的 **compute_metrics()** 函数并在我们下次训练时使用它。该函数必须采用 **EvalPrediction** 对象(带有 **predictions** 和 **label_ids** 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 **Trainer.predict()** 命令来使用我们的模型进行预测: +让我们看看如何构建一个有用的 `compute_metrics()` 函数,并在下次训练时使用它。该函数必须接收一个 `EvalPrediction` 对象(它是一个带有 `predictions` 和 `label_ids` 字段的参数元组),并将返回一个字符串映射到浮点数的字典(字符串是返回的指标名称,而浮点数是其值)。为了从我们的模型中获得预测结果,可以使用 `Trainer.predict()` 命令: ```py predictions = trainer.predict(tokenized_datasets["validation"]) @@ -100,9 +100,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) (408, 2) (408,) ``` - **predict()** 的输出结果是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 - -**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: +`predict()` 方法的输出另一个带有三个字段的命名元组: `predictions` `label_ids` 和 `metrics` `metrics` 字段将只包含所传递的数据集的损失,以及一些时间指标(总共花费的时间和平均预测时间)。当我们定义了自己的 `compute_metrics()` 函数并将其传递给 `Trainer` 该字段还将包含 `compute_metrics()` 返回的结果。`predict()` 是一个二维数组,形状为 408 × 2(408 是我们使用的数据集中的元素数量),这是我们传递给 `pprdict()` 的数据集中每个元素的 logits(正如在 [前一章](/course/chapter2) 中看到的,所有 Transformer 模型都返回 logits)。为了将它们转化为可以与我们的标签进行比较的预测值,我们需要找出第二个维度上取值最大的索引: ```py import numpy as np @@ -110,7 +108,7 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: +我们现在可以将这些 `preds` 与标签进行比较。为了构建我们的 `compute_metric()` 函数,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松地加载与 MRPC 数据集关联的指标,这次是使用 `evaluate.load()` 函数。返回的对象有一个 `compute()` 方法,我们可以用它来进行指标的计算: ```py import evaluate @@ -123,9 +121,9 @@ metric.compute(predictions=preds, references=predictions.label_ids) {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 +你得到的确切结果可能会有所不同,因为模型头部的随机初始化可能会改变其指标。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 MRPC 数据集在 GLUE 基准测试中的结果的两个指标。在 BERT 论文中的表格中,基础模型的 F1 分数为 88.9。那是 uncased 模型,而我们现在正在使用 cased 模型,这解释了为什么我们得到了更好的结果。 -最后将所有东西打包在一起,我们得到了我们的 **compute_metrics()** 函数: +最后把所有东西打包在一起,我们就得到了 `compute_metrics()` 函数: ```py def compute_metrics(eval_preds): @@ -135,7 +133,7 @@ def compute_metrics(eval_preds): return metric.compute(predictions=predictions, references=labels) ``` -为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : +为了查看模型在每个训练周期结束时的好坏,下面是我们如何使用 `compute_metrics()` 函数定义一个新的 `Trainer` ```py training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") @@ -152,21 +150,19 @@ trainer = Trainer( ) ``` -请注意,我们设置了了一个新的 **TrainingArguments** 它的**evaluation_strategy** 设置为 **epoch** 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行: +请注意,我们设置了一个新的 `TrainingArguments` ,其 `evaluation_strategy` 设置为 `epoch` 并且创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练我们已经训练过的模型。为了启动新的训练,我们执行: ```py trainer.train() ``` -这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 - -这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 +这一次,它将在每个 epoch 结束时在训练损失的基础上报告验证损失和指标。同样,由于模型的随机头部初始化,达到的准确率/F1 分数可能与我们发现的略有不同,这是由于模型头部的随机初始化造成的,但应该相差不多。 `Trainer` 可以在多个 GPU 或 TPU 上运行,并提供许多选项,例如混合精度训练(在训练的参数中使用 `fp16 = True` )。我们将在第十章讨论它支持的所有内容。 -使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在[第 7 章](/course/chapter7)中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 +使用 `Trainer` API 微调的介绍到此结束。在 [第七章](/course/chapter7) 中会给出一个对大多数常见的 NLP 任务进行训练的例子,但现在让我们看看如何在 PyTorch 中做相同的操作。 -✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 +✏️ **试试看!** 使用你在第 2 节中学到的数据处理过程,在 GLUE SST-2 数据集上对模型进行微调。 diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index 936c81e76..773224214 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -9,9 +9,9 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"}, ]} /> -完成上一节中的所有数据预处理工作后,您只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果您没有GPU,则可以在 [Google Colab](https://colab.research.google.com/) 上使用免费的 GPU 或 TPU(需要梯子)。 +一旦完成上一节中的所有数据预处理工作后,你只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果你没有GPU,你可以在 [Google Colab](https://colab.research.google.com)(国内网络无法使用) 上使用免费的 GPU 或 TPU。 -这一节的代码示例假设您已经执行了上一节中的代码示例。 下面一个简短的摘要,包含了在开始学习这一节之前您需要的执行的代码: +下面的代码示例假设你已经运行了上一节中的代码示例。 下面是在开始学习这一节之前你需要运行的代码: ```py from datasets import load_dataset @@ -48,17 +48,17 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -### 训练模型 [[训练模型]] +## 训练模型 [[训练模型]] 从🤗 Transformers 导入的 TensorFlow 模型已经是 Keras 模型。 下面的视频是对 Keras 的简短介绍。 -这意味着,一旦我们有了数据,就需要很少的工作就可以开始对其进行训练。 +这意味着一旦我们有了数据,只需要很少的工作就可以开始对其进行训练。 -和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类: +和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类,我们将有两个标签: ```py from transformers import TFAutoModelForSequenceClassification @@ -66,13 +66,13 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -您会注意到,与 [第二章](/course/chapter2) 不同的是,您在实例化此预训练模型后会收到警告。 这是因为 BERT 没有对句子对进行分类进行预训练,所以预训练模型的 head 已经被丢弃,而是插入了一个适合序列分类的新 head。 警告表明一些权重没有使用(对应于丢弃的预训练头),而其他一些权重是随机初始化的(新头的权重)。 最后鼓励您训练模型,这正是我们现在要做的。 +你会注意到,与 [第二章](/course/chapter2) 不同的是,在实例化这个预训练的模型后会收到警告。 这是因为 BERT 没有对句子对的分类进行预训练,所以预训练模型的 head 已经被丢弃,并且插入了一个适合序列分类的新 head。 警告表明,这些权重没有使用(对应于丢弃的预训练 head 权重),而其他一些权重是随机初始化的(对应于新 head 的权重)。 最后它建议你训练模型,这正是我们现在要做的。 -要在我们的数据集上微调模型,我们只需要在我们的模型上调用 `compile()` 方法,然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程(在 GPU 上应该需要几分钟)并输出训练loss,以及每个 epoch 结束时的验证loss。 +为了在我们的数据集上微调模型,我们只需要在我们的模型上调用 `compile()` 方法,然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程(在 GPU 上应该需要几分钟)并输出训练损失,以及每个 epoch 结束时的验证损失。 -请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的loss。 如果您没有在 `compile()` 中设置损失函数,他们将默认使用内部计算的损失。 请注意,要使用内部损失,您需要将标签作为输入的一部分传递,而不是作为单独的标签(这是在 Keras 模型中使用标签的正常方式)。 您将在课程的第 2 部分中看到这方面的示例,其中定义正确的损失函数可能很棘手。 然而,对于序列分类,标准的 Keras 损失函数可以正常工作,所以我们将在这里使用它。 +请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的损失。 如果你没有在 `compile()` 中设置损失参数,它们可以自动使用适当的损失函数,并在内部计算。 请注意,要使用内部损失,你需要将标签作为输入的一部分传入模型,而不是作为单独的标签(这是在 Keras 模型中使用标签的常规方式)。 你将在课程的第 2 部分中看到这方面的示例,正确定义损失函数可能会有些棘手。 然而对于序列分类来说,标准的 Keras 损失函数效果很好,因此我们将在这里使用它。 @@ -92,27 +92,28 @@ model.fit( -请注意这里有一个非常常见的陷阱——你只是*可以*将损失的名称作为字符串传递给 Keras,但默认情况下,Keras 会假设你已经对输出应用了 softmax。 然而,许多模型在应用 softmax 之前就输出,也称为 *logits*。 我们需要告诉损失函数我们的模型是否经过了softmax,唯一的方法是直接调用它,而不是用字符串的名称。 +请注意这里有一个非常常见的陷阱——你可以把损失的名称作为一个字符串传递给 Keras,但默认情况下,Keras 会假设你已经对输出进行了 softmax。 然而,许多模型在经过 softmax 函数之前输出的是被称为 `logits` 的值。 我们需要告诉损失函数,我们的模型是否已经使用 softmax 函数进行了处理,唯一的方法是传递一个损失函数并且在参数的部分告诉模型,而不是只传递一个字符串。 -### 提升训练的效果 [[提升训练的效果]] +## 改善训练的效果 [[改善训练的效果]] -如果您尝试上面的代码,它肯定会运行,但您会发现loss只是缓慢或零星地下降。 主要原因是*学习率*。 与loss一样,当我们将优化器的名称作为字符串传递给 Keras 时,Keras 会初始化该优化器具有所有参数的默认值,包括学习率。 但是,根据长期经验,我们知道Transformer 模型更适合使用比 Adam 的默认值(1e-3)也写成为 10 的 -3 次方,或 0.001,低得多的学习率。 5e-5 (0.00005) 比默认值大约低 20 倍,是一个更好的起点。 +如果你尝试上述代码,它的确可以运行,但你会发现损失下降得很慢或者不规律。 主要原因是`学习率`。 与损失一样,当我们把优化器的名称作为字符串传递给 Keras 时,Keras 会初始化该优化器具有所有参数的默认值,包括学习率。 不过根据长期经验,我们知道Transformer 模型的通常的最佳学习率比 Adam 的默认值(即1e-3,也写成为 10 的 -3 次方,或 0.001)低得多。 5e-5(0.00005),是一个更好的起始点。 -除了降低学习率,我们还有第二个技巧:我们可以慢慢降低学习率。在训练过程中。 在文献中,您有时会看到这被称为 *decaying* 或 *annealing*学习率。 在 Keras 中,最好的方法是使用 *learning rate scheduler*。 一个好用的是`PolynomialDecay`——尽管有这个名字,但在默认设置下,它只是简单地从初始值线性衰减学习率值在训练过程中的最终值,这正是我们想要的。但是, 为了正确使用调度程序,我们需要告诉它训练的次数。 我们将在下面为其计算“num_train_steps”。 +除了降低学习率之外,我们还有第二个技巧:我们可以在训练过程中慢慢降低学习率。在文献中,你有时会看到这被称为 学习率的`衰减(decaying)` 或 `退火(annealing)`。 在 Keras 中,最好的方法是使用 `学习率调度器(learning rate scheduler)`。 一个好用的调度器是`PolynomialDecay`——尽管它的名字叫`PolynomialDecay(多项式衰减)`,但在默认设置下,它只是简单将学习率从初始值线性衰减到最终值,这正是我们想要的。不过为了正确使用调度程序,我们需要告诉它训练的次数。我们可以通过下面的`num_train_steps`计算得到。 ```py from tensorflow.keras.optimizers.schedules import PolynomialDecay batch_size = 8 num_epochs = 3 -# 训练步数是数据集中的样本数除以batch size再乘以 epoch。 -# 注意这里的tf_train_dataset是一个转化为batch后的 tf.data.Dataset, -# 不是原来的 Hugging Face Dataset,所以它的 len() 已经是 num_samples // batch_size。 + +# 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。 +# 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset, +# 而不是原始的 Hugging Face Dataset ,所以使用 len() 计算它的长度已经是 num_samples // batch_size。 num_train_steps = len(tf_train_dataset) * num_epochs lr_scheduler = PolynomialDecay( initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps @@ -124,11 +125,11 @@ opt = Adam(learning_rate=lr_scheduler) -🤗 Transformers 库还有一个 `create_optimizer()` 函数,它将创建一个具有学习率衰减的 `AdamW` 优化器。 这是一个便捷的方式,您将在本课程的后续部分中详细了解。 +🤗 Transformers 库还有一个 `create_optimizer()` 函数,它将创建一个具有学习率衰减的 `AdamW` 优化器。 这是一个快捷的方式,你将在本课程的后续部分中详细了解。 -现在我们有了全新的优化器,我们可以尝试使用它进行训练。 首先,让我们重新加载模型,以重置我们刚刚进行的训练运行对权重的更改,然后我们可以使用新的优化器对其进行编译: +现在我们有了全新的优化器,我们可以尝试使用它进行训练。 首先,让我们重新加载模型,重新设置刚刚训练时的权重,然后我们可以使用新的优化器对其进行编译: ```py import tensorflow as tf @@ -146,22 +147,22 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -💡 如果您想在训练期间自动将模型上传到 Hub,您可以在 `model.fit()` 方法中传递 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进行介绍 +💡 如果你想在训练期间自动将模型上传到 Hub,你可以在 `model.fit()` 方法中传递一个 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进一步了解这个问题。 -### 模型预测 [[模型预测]] +## 模型预测 [[模型预测]] -训练和观察的loss下降都非常好,但是如果我们想从训练后的模型中获得输出,或者计算一些指标,或者在生产中使用模型呢? 为此,我们可以使用`predict()` 方法。 这将返回模型的输出头的*logits*数值,每个类一个。 +训练和观察损失的下降都是非常好,但如果我们想从训练后的模型中得到输出,或者计算一些指标,或者在生产中使用模型,该怎么办呢?为此,我们可以使用`predict()` 方法。 这将返回模型的输出头的`logits`数值,每个类一个。 ```py preds = model.predict(tf_validation_dataset)["logits"] ``` -我们可以将这些 logit 转换为模型的类别预测,方法是使用 argmax 找到最高的 logit,它对应于最有可能的类别: +我们可以通过使用 argmax 将这些 logits 转换为模型的类别预测,最高的 logits,对应于最有可能的类别 ```py class_preds = np.argmax(preds, axis=1) @@ -172,7 +173,7 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行度量计算: +现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行指标的计算: ```py import evaluate @@ -185,6 +186,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会改变它获得的指标。 在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 得分为 89.97。 这些是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这解释了为什么我们会获得更好的结果。 +由于模型头的随机初始化可能会改变模型达到的指标,因此你得到的最终结果可能会有所不同。在这里,我们可以看到我们的模型在验证集上的准确率为85.78%,F1分数为89.97。 这就是用于评估 GLUE 基准的 MRPC 数据集上的结果两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这也可以解释为什么我们会得到了更好的结果。 -使用 Keras API 进行微调的介绍到此结束。 [第 7 章](/course/chapter7)将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能,请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file +关于使用 Keras API 进行微调的介绍到此结束。 在[第七章](/course/chapter7)将给出对大多数常见 NLP 任务微调或训练的示例。如果你想在 Keras API 上磨练自己的技能,请尝试使第2节所学的的数据处理技巧在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index bc8921f13..743f763f1 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -9,7 +9,7 @@ -现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: +现在,我们将了解如何在不使用 `Trainer` 类的情况下实现与上一节相同的结果。同样,我们假设你已经完成了第 2 节中的数据处理。下面对第 2 节内容的一个简短总结,涵盖了你需要在本节之前运行的所有内容: ```py from datasets import load_dataset @@ -28,15 +28,15 @@ tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` -### 训练前的准备 [[训练前的准备]] +## 训练前的准备 [[训练前的准备]] -在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: +在正式开始编写我们的训练循环之前,我们需要定义一些对象。首先是我们将用于迭代 batch 的数据加载器。但在定义这些数据加载器之前,我们需要对我们的 `tokenized_datasets` 进行一些后处理,以自己实现一些 Trainer 自动为我们处理的内容。具体来说,我们需要: -- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 -- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 +- 删除与模型不需要的列(如 `sentence1` 和 `sentence2` 列)。 +- 将列名 `label` 重命名为 `labels` (因为模型默认的输入是 `labels` )。 - 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 -针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: +针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: ```py tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) @@ -45,13 +45,13 @@ tokenized_datasets.set_format("torch") tokenized_datasets["train"].column_names ``` -然后,我们可以检查结果中是否只有模型能够接受的列: +然后,我们可以检查结果中是否只有模型能够接受的列: ```python ["attention_mask", "input_ids", "labels", "token_type_ids"] ``` -至此,我们可以轻松定义数据加载器: +至此,我们可以轻松定义数据加载器: ```py from torch.utils.data import DataLoader @@ -64,7 +64,7 @@ eval_dataloader = DataLoader( ) ``` -为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: +为了快速检验数据处理中没有错误,我们可以这样检验其中的一个 batch: ```py for batch in train_dataloader: @@ -79,16 +79,17 @@ for batch in train_dataloader: 'token_type_ids': torch.Size([8, 65])} ``` -请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 +请注意,这里的形状可能与你略有不同,因为我们为训练数据加载器设置了 `shuffle=True` ,并且模型会将句子填充到 `batch` 中的最大长度。 -现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: +现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们会像在上一节中所做的那样实例化它: ```py from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: + +为了确保训练过程中一切顺利,我们将 `batch` 传递给这个模型: ```py outputs = model(**batch) @@ -99,9 +100,9 @@ print(outputs.loss, outputs.logits.shape) tensor(0.5441, grad_fn=) torch.Size([8, 2]) ``` -当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 +当我们输入 `labels` 时,🤗 Transformers 模型都将返回这个 `batch` 的 `loss` ,我们还得到了 `logits` ( `batch` 中的每个输入有两个输出,所以张量大小为 8 x 2)。 -我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): +我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图手动实现 `Trainer` 的功能,我们将使用相同的优化器和学习率调度器。 `Trainer` 使用的优化器是 `AdamW` ,它与 `Adam` 相同,但加入了权重衰减正则化的一点变化(参见 Ilya Loshchilov 和 Frank Hutter 的 [“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101) ): ```py from transformers import AdamW @@ -109,7 +110,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: +最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的 batch 的数量(即我们训练数据加载器的长度)。 `Trainer` 默认情况下使用三个 `epochs` ,因此我们定义训练过程如下: ```py from transformers import get_scheduler @@ -129,9 +130,9 @@ print(num_training_steps) 1377 ``` -### 训练循环 [[训练循环]] +## 训练循环 [[训练循环]] -最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: +最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device` ,它在 GPU 可用的情况下指向 GPU,最后我们将把我们的模型和 `batch` 放在 `device` 上: ```py import torch @@ -145,7 +146,7 @@ device device(type='cuda') ``` -我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: +我们现在准备好训练了!为了知道训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: ```py from tqdm.auto import tqdm @@ -166,12 +167,12 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 +你可以看到训练循环的核心与介绍中的非常相似。我们没有要求在训练的过程中进行检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 -### 评估循环 [[评估循环]] +## 评估循环 [[评估循环]] -正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: +正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()` 方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 评估得到的结果。以下是如何在评估循环中实现所有这些的方法: ```py import evaluate @@ -194,19 +195,19 @@ metric.compute() {'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} ``` -同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 +同样,由于模型头部初始化和数据打乱的随机性,你的结果会略有不同,但应该相差不多。 -✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 +✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调你的模型。 -### S使用🤗 Accelerate加速您的训练循环 [[S使用🤗 Accelerate加速您的训练循环]] +## 使用🤗 Accelerate 加速你的训练循环 [[使用🤗 Accelerate加速你的训练循环]] -我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: +我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。通过使用 [🤗 Accelerate](https://github.com/huggingface/accelerate) 库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: ```py from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler @@ -242,7 +243,7 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -以下是变化: +以下是更改的部分: ```diff + from accelerate import Accelerator @@ -286,15 +287,17 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 +要添加的第一行是导入 `Accelerator` 。第二行实例化一个 `Accelerator` 对象 它将查看环境并初始化适当的分布式设置。🤗 Accelerate 为你处理数据在设备间的数据传递,因此你可以删除将模型放在设备上的那行代码(或者,如果你愿意,可使用 `accelerator.device` 代替 `device` )。 -然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 +然后大部分工作会在将数据加载器、模型和优化器发送到的 `accelerator.prepare()` 中完成。这将会把这些对象包装在适当的容器中,以确保你的分布式训练按预期工作。要进行的其余更改是删除将 `batch` 放在 `device` 的那行代码(同样,如果你想保留它,你可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为 `accelerator.backward(loss)` 。 -⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 + +⚠️ 为了使云端 TPU 提供的加速中发挥最大的效益,我们建议使用 tokenizer 的 `padding=max_length` 和 `max_length` 参数将你的样本填充到固定长度。 + -如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: +如果你想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: ```py from accelerate import Accelerator @@ -333,13 +336,13 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: +把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: ```bash accelerate config ``` -这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: +这将询问你几个配置的问题并将你的回答保存到此命令使用的配置文件中: ``` accelerate launch train.py @@ -347,7 +350,7 @@ accelerate launch train.py 这将启动分布式训练 -这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: +这将启动分布式训练。如果你想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到一个 `training_function()` 函数中,并在最后一个单元格中运行: ```python from accelerate import notebook_launcher @@ -355,4 +358,4 @@ from accelerate import notebook_launcher notebook_launcher(training_function) ``` -您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 +你可以在 [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples) 找到更多的示例。 diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx index ee69b3a3a..dce6ede80 100644 --- a/chapters/zh-CN/chapter3/5.mdx +++ b/chapters/zh-CN/chapter3/5.mdx @@ -1,23 +1,25 @@ -# 微调,检查! [[微调,检查!]] +# 微调,检查![[微调,检查!]] -这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: +这是非常令人高兴的!在前两章中,你了解了模型和 Tokenizer,现在你知道如何针对你自己的数据对它们进行微调。回顾一下,在本章中,你: {#if fw === 'pt'} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 -* 实现您自己的模型微调和评估 -* 实施了一个较为底层的训练循环 -* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU + +* 了解了 [Hub](https://huggingface.co/datasets) 中的数据集 +* 学习了如何加载和预处理数据集,包括使用动态填充和数据整理器 +* 实现你自己的模型微调和评估 +* 实现了一个较为底层的训练循环 +* 使用 🤗 Accelerate 轻松调整你的训练循环,使其适用于多个 GPU 或 TPU {:else} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 + +* 了解了 [Hub](https://huggingface.co/datasets) 中的数据集 * 学习了如何加载和预处理数据集 * 学习了如何使用 Keras 微调和评估模型 * 实现了自定义指标 diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx index bf93482ba..fa29ddd94 100644 --- a/chapters/zh-CN/chapter3/6.mdx +++ b/chapters/zh-CN/chapter3/6.mdx @@ -9,47 +9,46 @@ classNames="absolute z-10 right-0 top-0" /> -Test what you learned in this chapter! +现在来测试一下本章所学内容吧! - -### 1.`emotion`数据集包含标记有情绪的 Twitter 消息。在[Hub](https://huggingface.co/datasets)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? +### 1. “emotion”数据集包含带有情绪标记的 Twitter 消息。请在 [ Hub ]( https://huggingface.co/datasets) 中进行搜索并读取数据集的数据卡片。判断哪一个基本情感不在这个数据集中? -### 2.在[Hub](https://huggingface.co/datasets)中搜索`ar_sarcasm`数据集,它支持哪个任务? +### 2. 在 [ Hub ]( https://huggingface.co/datasets) 中搜索`ar_sarcasm`数据集,该数据集支持哪个任务? 数据集卡 !" + explain: "不对,请再看看[数据卡片](https://huggingface.co/datasets/ar_sarcasm)!" }, { text: "命名实体识别", - explain: "不是这样的ーー再看看 数据集卡 !" + explain: "不对,请再看看[数据卡片](https://huggingface.co/datasets/ar_sarcasm) !" }, { text: "回答问题", @@ -58,215 +57,211 @@ Test what you learned in this chapter! ]} /> -### 3.BERT模型期望如何处理一对句子? +### 3. 当输入一对句子时 BERT 模型会需要进行怎么样的预处理? [SEP]标记来分割两个句子,但这并不是唯一的需求!" + text: "句子1的token序列 [ SEP ] 句子2的token序列", + explain: "需要使用一个特殊的 `[SEP]` token 来分隔两个句子,但是只有这一个还不够。" }, { - text: "[CLS]句子1 句子2", - explain: "确实在最开始需要一个[CLS]标记的,但是这不是唯一的事情!" + text: "[CLS] 句子1的token序列 句子2的token序列", + explain: "需要一个特殊的 `[CLS]` token 来指示句子的开头,但是只有这一个还不够。" }, { - text: "[CLS]句子1 [SEP] 句子2 [SEP]", - explain: "没错!", + text: "[CLS] 句子1的token序列 [SEP] 句子2的token序列 [SEP]", + explain: "正确!", correct: true }, { - text: "[CLS]句子1 [SEP] 句子2", - explain: "开头需要一个[CLS]特殊标记,还需要一个[SEP]特殊标记来分隔两个句子,但这还不是全部!" + text: "[CLS] 句子1的token序列 [SEP] 句子2的token序列", + explain: "需要一个特殊的 `[CLS]` token 来指示句子的开头,还需要一个特殊的 `[SEP]` token 来分隔两个句子,但这还不是需要全部的预处理。" } ]} /> {#if fw === 'pt'} -### 4.`Dataset.map()`方法的好处是什么? +### 4. `Dataset.map ()`方法的好处是什么? -### 5.什么是动态填充? +### 5. 什么是动态填充? -### 6.校对函数的用途是什么? +### 6. collate 函数的用途是什么? DataCollatorWithPadding。" + explain: "collate 函数用于处理单个 batch 处理,而不是整个数据集。此外,我们讨论的是所有 collate 函数通常的用途,而不是特定的 DataCollatorWithPadding " }, { - text: "它把所有的样品放在一起成一批", - explain: "正确的!可以将校对函数作为DataLoader的参数传递。我们使用了DataCollatorWithPadding函数,该函数填充批处理中的所有项,使它们具有相同的长度。", - correct: true + text:"它把所有的样本地放在一个 batch 里。", + explain:"正确!你可将 collate 函数作为 DataLoader函数的一个参数。 我们使用了 DataCollatorWithPadding 函数, 该函数对批次中的所有项目进行填充,使它们具有相同的长度。", + correct:true }, { - text: "它对整个数据集进行预处理。", - explain: "这将是一个预处理函数,而不是校对函数。" + text: "它预处理整个数据集。", + explain: "预处理函数(preprocessing)用于预处理整个数据集,而不是 collate 函数。" }, { text: "它截断数据集中的序列。", - explain: "校对函数用于处理单个批次,而不是整个数据集。如果您对截断感兴趣,可以使用标记器truncate参数。" + explain: "collate 函数用于处理单个 batch 的处理,而不是整个数据集。如果你对截断感兴趣,可以使用 tokenizer truncate 参数。" } ]} /> -### 7.当你用一个预先训练过的语言模型(例如`bert-base-uncased`)实例化一个`AutoModelForXxx`类,这个类对应于一个不同于它所被训练的任务时会发生什么? +### 7. 当你用一个预先训练过的语言模型(例如 `bert-base-uncased`)实例化一个`AutoModelForXxx`类,这个类与它所被训练的任务不匹配时会发生什么? AutoModelForSequenceClassification配合bert-base-uncase 时,我们在实例化模型时会得到警告。预训练的头部不用于序列分类任务,因此它被丢弃,并用随机权重实例化一个新的头部。", + text: "丢弃预训练模型的头部,取而代之的是一个适合该任务的新头部。", + explain: "正确的。 例如,当我们将 AutoModelForSequenceClassification 与 bert-base-uncased 结合使用时,我们在实例化模型时将收到警告。 预训练的头不用于序列分类任务,因此它被丢弃,并使用随机初始化权重的用于序列分类任务的头。", correct: true }, { text: "丢弃预先训练好的模型头部。", - explain: "还有其他事情需要发生。 再试一次!" + explain: "还需要做其他事情,再试一次!" }, { - text: "无事发生,因为模型仍然可以针对不同的任务进行微调。", - explain: "预训练模型的头部没有被训练来解决这个任务,所以我们应该丢弃头部!!" + text: "没有,因为模型仍然可以针对不同的任务进行微调。", + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢弃该头部!" } ]} /> -### 8.`TrainingArguments`的目的是什么? +### 8.`TrainingArguments`的用途是什么? 训练器进行训练和评估的所有超参数。", + text: "它包含了所有用于训练和评估的超参数。", explain: "正确!", correct: true }, { text: "它指定模型的大小。", - explain: "模型大小是由模型配置定义的,而不是类TrainingArguments。" + explain: "模型大小是由模型配置定义的,而不是由 `TrainingArguments` 类 。" }, { text: "它只包含用于评估的超参数。", - explain: "在举例中,我们指定了模型及其检查点的保存位置。再试一次!" - }, - { - text: "它只包含用于训练的超参数。", - explain: "在举例中,我们还使用了求值策略,因此这会影响求值。再试一次!" + explain: "在示例中,我们还指定了模型的超参数及其检查点的保存位置。 再试一次!" } ]} /> -### 9.为什么要使用 🤗Accelerate 库? +### 9.为什么要使用🤗 Accelerate 库? Trainer所做的,而不是🤗Accelerate库。再试一次!”" + text: "它提供了一个高级 API,因此我不必实现自己的训练循环。", + explain: "这是我们使用 Trainer 所做的事情,而不是 🤗 Accelerate 库。 再试一次" }, { - text: "它使我们的训练循环工作在分布式策略上", - explain: "正确! 随着🤗Accelerate库,你的训练循环将为多个GPU和TPU工作。", + text: "它使我们的训练循环运行在分布式架构上", + explain: "正确! 使用🤗 Accelerate 库,你的训练循环可以在多个 GPU 和 TPUs 上运行。", correct: true }, { text: "它提供了更多的优化功能。", - explain: "不,🤗Accelerate 库不提供任何优化功能。" + explain: "不,🤗 Accelerate 库不提供任何优化功能。" } ]} /> {:else} -### 4.当你用一个预先训练过的语言模型(例如`bert-base-uncased`)实例化一个`TFAutoModelForXxx`类时,会发生什么? +### 4.当模型与预训练的任务不匹配时,例如使用预训练的语言模型(例如“`bert-base-uncased`”)实例化“`TFAutoModelForXxx`”类时会发生什么? TFAutoModelForSequenceClassification配合bert-base-uncase时,我们在实例化模型时会得到警告。预训练的头部不用于序列分类任务,因此它被丢弃,并用随机权重实例化一个新的头部。", + text: "丢弃预训练模型的头部,并插入一个新的头部以适应新的任务。", + explain: "正确的。 例如,当我们将 `TFAutoModelForSequenceClassification `与 `bert-base-uncased` 结合使用时,我们在实例化模型时收到警告。 预训练的头不用于序列分类任务,因此它被丢弃,使用新的头并且随机初始化权重。", correct: true }, { text: "丢弃预先训练好的模型头部。", - explain: "还有其他事情需要发生。 再试一次!" + explain: "除此之外还有一些事情会发生, 再试一次!" }, { - text: "无事发生,因为模型仍然可以针对不同的任务进行微调。", - explain: "预训练模型的头部没有被训练来解决这个任务,所以我们应该丢弃头部!!" + text: "没有,因为模型仍然可以针对不同的任务进行微调。", + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" } ]} /> -### 5.来自`transfomers`的 TensorFlow 模型已经是 Keras 模型,这有什么好处? +### 5.来自 `transformers` 的 TensorFlow 模型已经是 Keras 模型,这有什么好处? TPUStrategy范围内运行所有内容,包括模型的初始化。" + text: "这些模型可在开箱即用的 TPU 上运行。", + explain: "差不多!但是还需要进行一些小的额外修改。例如,你需要在 TPUStrategy 范围内运行所有内容,包括模型的初始化。" }, { - text: "您可以利用现有的方法,如compile()fit()predict() 。", - explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", + text: "你可以利用现有方法,例如 compile()fit()predict()。", + explain: "正确! 一旦你有了数据,只需要很少的工作就可以在这些数据上进行训练。", correct: true }, { - text: "你可以学习 Keras 和 transformers库。", + text: "你可以学习 Keras 以及 Transformer。", explain: "没错,但我们要找的是别的东西:)", correct: true }, { - text: "您可以轻松地计算与数据集相关的指标。", + text: "你可以轻松计算与数据集相关的指标。", explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的指标。" } ]} /> -### 6.如何定义自己的定制指标? +### 6.如何定义自己的自定义指标? tf.keras.metrics.Metric。", + text: "使用子类化 tf.keras.metrics.Metric。", explain: "太好了!", correct: true }, @@ -275,13 +270,13 @@ Test what you learned in this chapter! explain: "再试一次!" }, { - text: "通过使用带签名的可调用metric_fn(y_true, y_pred) 。", + text: "使用使用带有签名的 metric_fn(y_true, y_pred) 函数。", explain: "正确!", correct: true }, { - text: "通过谷歌搜索。", - explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", + text: "使用谷歌搜索。", + explain: "这不是我们要找的答案,不过它应该能帮助你找到答案。", correct: true } ]} diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx index 32c8ef87e..2bf33daab 100644 --- a/chapters/zh-CN/chapter4/1.mdx +++ b/chapters/zh-CN/chapter4/1.mdx @@ -5,16 +5,18 @@ classNames="absolute z-10 right-0 top-0" /> -[Hugging Face Hub](https://huggingface.co/)---我们的主网站,是一个中央平台,在这个网站上任何人都可以查找、使用和贡献新的最先进的模型和数据集。它拥有各种各样的模型,公开可用的模型超过 10,000个。我们在本章去探索Hub中的模型,并在第 5 章中探索Hub中的数据集。 +我们的主网站——— [Hugging Face中心](https://huggingface.co/) 是一个集发现、使用及贡献最新先进模型与数据集为一体的中心平台。这里汇聚了超过 10,000 个公开可用的各种领域的模型。我们将在本章节专注探讨这些模型,并在第五章节深入讨论数据集。 -Hub 中的模型不仅限于🤗 Transformers 甚至 NLP。有用于自然语言处理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用于音频检测的[pyannote](https://github.com/pyannote/pyannote-audio),以及对于视觉的[timm](https://github.com/rwightman/pytorch-image-models),这些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 +Hub 中的模型不仅限于🤗 Transformers 或者是自然语言处理。这里有许多模型,比如用于自然语言处理的 [Flair](https://github.com/flairNLP/flair) 和 [AllenNLP](https://github.com/allenai/allennlp) 模型,用于音频检测的 [Asteroid](https://github.com/asteroid-team/asteroid) 和 [pyannote](https://github.com/pyannote/pyannote-audio) 模型,以及用于视觉的 [timm](https://github.com/rwightman/pytorch-image-models) 模型等,这些例子只是 Hub 中模型的冰山一角,更多的模型可以由你去探索。 -这些模型中的每一个都作为 Git 存储库托管,这允许进行版本控制和重现。在 Hub 上共享模型意味着将其向社区开放,让任何希望使用它的人都可以轻松访问它,从而使其他人不用为训练模型而苦恼就可以直接使用模型。 +这些模型中的每一个都以 Git 仓库的形式托管,方便进行版本控制和复现。在 Hub 上共享模型意味着向社区开放,让任何希望使用模型的人都可以轻松访问,从而使他们免于训练模型之苦,并且简化了模型分享和使用。 -此外,在 Hub 上共享模型会自动为该模型部署托管的推理 API。社区中的任何人都可以直接在模型页面上自由地测试它,使用自定义输入和适当的小部件。 +此外,在 Hub 上分享一个模型会自动为该模型部署一个推理 API。社区中的任何人都可以直接在模型页面上使用各自的输入和相应的小工具来进行测试。 -最棒的是是在 Hub 上共享和使用任何公共模型是完全免费的!如果您不想公开模型,也存在[付费计划](https://huggingface.co/pricing)。下面的视频显示了如何使用 Hub。 +最妙的是,在 Hub 上分享和使用任何公共模型都是完全免费的!如果你希望只把模型分享给特定的人,我们也有 [付费计划](https://huggingface.co/pricing) 。 + +下面的视频展示了如何使用 Hub。 -这部分需要有一个 Huggingface.co 帐户,因为我们将在 Hugging Face Hub 上创建和管理存储库:[创建一个账户](https://huggingface.co/join) \ No newline at end of file +如果你想跟进实现这一部分内容,你需要有一个 Huggingface.co 帐户,因为这一节我们将在 Hugging Face Hub 上创建和管理存储库: [创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx index e40ba9d79..956ecd244 100644 --- a/chapters/zh-CN/chapter4/2.mdx +++ b/chapters/zh-CN/chapter4/2.mdx @@ -22,15 +22,15 @@ {/if} -模型中心使选择合适的模型变得简单,因此只需几行代码即可在任何下游库中使用它。让我们来看看如何使用这些模型,以及如何将模型贡献到社区。 +模型中心使选择合适的模型变得简单,只需几行代码即可在任何下游库中使用它。让我们来看看如何使用这些模型,以及如何将模型贡献到社区。 -假设我们正在寻找一种可以执行 mask 填充的 French-based 模型。 +假设我们正在寻找一种可以执行掩码填充(mask filling 又称完形填空)的 French-based(法语)模型。
Selecting the Camembert model.
-我们选择 `camembert-base` 检查点来尝试一下。我们需要做的仅仅是输入 `camembert-base` 标识符!正如你在前几章中看到的,我们可以使用 `pipeline()` 功能: +我们选择 `camembert-base` checkpoint 来尝试一下。我们需要做的仅仅是输入 `camembert-base` 标签!正如你在前几章中学习的,我们可以使用 `pipeline()` 功能: ```py from transformers import pipeline @@ -49,13 +49,13 @@ results = camembert_fill_mask("Le camembert est :)") ] ``` -如你所见,在管道中加载模型非常简单。你唯一需要注意的是所选检查点是否适合它将用于的任务。例如,这里我们正在将 `camembert-base` 检查点加载在 `fill-mask` 管道,这完全没问题。但是如果我们在 `text-classification` 管道加载检查点,结果没有任何意义,因为 `camembert-base` 不适合这个任务!我们建议使用 Hugging Face Hub 界面中的任务选择器来选择合适的检查点: +如你所见,在管道中加载模型非常简单。你唯一需要注意的是所选 checkpoint 是否适合它将用于的任务。例如,这里我们正在将 `camembert-base` checkpoint 加载在 `fill-mask` 管道,这完全没问题。但是如果我们在 `text-classification` 管道中加载该 checkpoint 结果没有任何意义,因为 `camembert-base` 不适合这个任务!我们建议使用 Hugging Face Hub 中的任务选择器来选择合适的 checkpoint 。
The task selector on the web interface.
-你还可以直接使用模型架构实例化检查点: +你还可以直接使用模型架构实例化 checkpoint: {#if fw === 'pt'} ```py @@ -65,7 +65,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = CamembertForMaskedLM.from_pretrained("camembert-base") ``` -然而,我们建议使用 [`Auto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `Auto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `Auto*` 类使切换不同的检查点变得简单: +然而,我们建议使用 [`Auto*` 类](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) ,因为 `Auto*` 类在设计时不依赖模型架构。前面的代码示例将只能在 CamemBERT 架构中加载可用的 checkpoint,但使用 `Auto*` 类使切换不同的 checkpoint 变得简单: ```py from transformers import AutoTokenizer, AutoModelForMaskedLM @@ -81,7 +81,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = TFCamembertForMaskedLM.from_pretrained("camembert-base") ``` -然而,我们建议使用 [`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `TFAuto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `TFAuto*` 类使切换不同的检查点变得简单: +然而,我们建议使用 [`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) ,因为 `TFAuto*` 类在设计时不依赖模型架构。前面的代码示例将只能在 CamemBERT 架构中加载可用的 checkpoint,但使用 `TFAuto*` 类使切换不同的 checkpoint 变得简单: ```py from transformers import AutoTokenizer, TFAutoModelForMaskedLM @@ -92,5 +92,5 @@ model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") {/if} -使用预训练模型时,一定要检查它是如何训练的、在哪些数据集上训练的、它的局限性和偏差。所有这些信息都应在其模型卡片上注明。 +使用预训练模型时,一定要检查它是如何训练的、在哪些数据集上训练的、它的局限性和偏见。所有这些信息都应在其模型卡片上有所展示。 diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index ba5e26550..a10f1e13d 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -22,22 +22,21 @@ {/if} -在下面的步骤中,我们将看看将预训练模型分享到 🤗 Hub 的最简单方法。有可用的工具和实用程序可以让直接在 Hub 上共享和更新模型变得简单,我们将在下面进行探讨。 +接下来,我们将探索把预训练模型分享到 🤗 Hub 最便捷的方法。我们将一同研究一些工具和功能,它们可以简化直接在中心上分享和更新模型的流程。 -我们鼓励所有训练模型的用户通过与社区共享来做出贡献——共享模型,即使是在非常特定的数据集上进行训练,也将帮助他人,节省他们的时间和计算资源,并提供对有用的训练工件的访问。反过来,您可以从其他人所做的工作中受益! +我们鼓励所有训练模型的用户通过与社区共享来做出贡献——即使是在特定数据集上训练的模型的分享,也能帮助他人,节省他们的时间和计算资源,并提供一些有用的训练成果。反过来,你可以从其他人所做的工作中受益! -创建新模型存储库的方法有以下三种: +创建模型存储仓库的方法有以下三种: -- 使用 push_to_hub API 接口 -- 使用 huggingface_hub Python 库 -- 使用 web 界面 +- 使用 `push_to_hub` API 接口 +- 使用 `huggingface_hub` Python 库 +- 使用网页界面 -创建存储库后,您可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分引导您创建模型存储库并将文件上传到它们 +创建仓库后,你可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分带领你创建模型仓库并向其中上传文件。 - -## 使用 push_to_hub API [[使用 push_to_hub API]] +## 使用 `push_to_hub` API [[使用 push_to_hub API]] {#if fw === 'pt'} @@ -49,9 +48,11 @@ {/if} -将文件上传到模型中心的最简单方法是利用 **push_to_hub** API 接口。 +将文件上传到 hub 的最简单方法是利用 `push_to_hub` API 接口。 + +在进行下一步操作之前,你需要生成一个身份验证令牌,这样 `huggingface_hub` API 才会知道你是谁以及你对哪些空间具有写入权限。 -在继续之前,您需要生成一个身份验证令牌,以便 **huggingface_hub** API 知道您是谁以及您对哪些名称空间具有写入权限。确保你在一个环境中 **transformers** 已安装(见[安装](/course/chapter0))。如果您在笔记本中,可以使用以下功能登录: +在一个安装了 `transformers` 环境中(参见 [第零章](/course/chapter0) )。如果你在 notebook 中,可以运行以下代码登录: ```python from huggingface_hub import notebook_login @@ -59,19 +60,19 @@ from huggingface_hub import notebook_login notebook_login() ``` -在终端中,您可以运行: +在终端中,你可以运行: ```bash huggingface-cli login ``` -在这两种情况下,系统都会提示您输入用户名和密码,这与您用于登录 Hub 的用户名和密码相同。如果您还没有 Hub 配置文件,则应该创建一个[创建一个账户](https://huggingface.co/join)。 +在这两种情况下,系统都会提示你输入用户名和密码,这与你用于登录 Hub 的用户名和密码相同。如果你还没有 Huggingface 账户,则应该 [在这里](https://huggingface.co/join) 创建一个账户。 -好的!您现在已将身份验证令牌存储在缓存文件夹中。让我们创建一些存储库! +好极了!现在已将你的身份验证令牌存储在了缓存文件夹中。让我们创建一些仓库! {#if fw === 'pt'} -如果你玩过 **Trainer** 用于训练模型的 API,将其上传到 Hub 的最简单方法是设置 **push_to_hub=True** 当你定义你的 **TrainingArguments** : +如果你已经尝试使用过 `Trainer` API 训练模型,将其上传到 Hub 的最简单方法是在定义 `TrainingArguments` 时设置 `push_to_hub=True` : ```py from transformers import TrainingArguments @@ -81,11 +82,11 @@ training_args = TrainingArguments( ) ``` -你声明 **trainer.train()** 的时候, 这 **Trainer** 然后每次将您的模型保存到您的命名空间中的存储库中时(这里是每个时代),它将上传到模型中心。该存储库将命名为您选择的输出目录(此处 **bert-finetuned-mrpc** ) 但您可以选择不同的名称 **hub_model_id = a_different_name** 。 +这样,调用 `trainer.train()` 的时候, `Trainer` 会在每次保存模型时(这里是每个训练周期)将其上传到 Hub 中你的账户下的一个仓库。该仓库的名称与你选择的输出目录名称相同(这里是 `bert-finetuned-mrpc` ),但你可以通过 `hub_model_id = "a_different_name"` 指定一个不同的名称。 -要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 +若要将你的模型上传到你所属的组织,只需设置 `hub_model_id = my_organization/my_repo_name` 。 -训练结束后,你应该做最后的 **trainer.push_to_hub()** 上传模型的最新版本。它还将生成包含所有相关元数据的模型卡,报告使用的超参数和评估结果!以下是您可能会在此类模型卡中找到的内容示例: +最后,需要在训练循环结束后运行 `trainer.push_to_hub()` 上传模型的最新版本。它还会生成一份包含所有相关元数据的模型卡片,包含使用的超参数和评估结果!以下是一个模型卡片的示例:
An example of an auto-generated model card. @@ -95,7 +96,7 @@ training_args = TrainingArguments( {:else} -如果您使用Keras来训练您的模型,则将其上传到Hub的最简单方法是在调用 **model.fit()** 时传递**PushToHubCallback**: +如果你使用 Keras 来训练你的模型,则将其上传到 Hub 的最简单方法是在调用 `model.fit()` 添加 `PushToHubCallback` : ```py from transformers import PushToHubCallback @@ -105,15 +106,15 @@ callback = PushToHubCallback( ) ``` -然后,您应该在对**model.fit()**的调用中添加**callbacks=[callback]**。然后,每次将模型保存在命名空间的存储库中(此处为每个 epoch)时,回调都会将模型上传到 Hub。该存储库的名称将类似于您选择的输出目录(此处为**bert-finetuned-mrpc**),但您可以选择另一个名称,名称为**hub_model_id = a_different_name**。 +然后,你应该在调用 `model.fit()` 时添加 `callbacks=[callback]` 。回调函数将在每次保存模型时(这里是每个训练周期)将你的模型上传到 Hub 的你的账户下的一个仓库。该仓库的名称你选择的输出目录名称相同(此处为 `bert-finetuned-mrpc` ),但你可以通过 `hub_model_id = "a_different_name"` 指定一个不同的名称。 -要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 +要将你的模型上传到你所属的组织,只需设置如下 `hub_model_id = my_organization/my_repo_name` 。 {/if} -在较低级别,可以通过模型、标记器和配置对象直接访问模型中心 **push_to_hub()** 方法。此方法负责创建存储库并将模型和标记器文件直接推送到存储库。与我们将在下面看到的 API 不同,不需要手动处理。 +在更底层,你可以通过直接在模型、tokenizer 和配置对象上调用 `push_to_hub()` 方法来访问模型中心。该方法能够同时创建仓库并直接将模型和 tokenizer 文件推送至仓库,无需手动操作,这与我们在下面要看到的 API 有些许不同。 -为了了解它是如何工作的,让我们首先初始化一个模型和一个标记器: +为了了解它是如何工作的,让我们首先创建一个模型和一个 tokenizer {#if fw === 'pt'} @@ -139,34 +140,35 @@ tokenizer = AutoTokenizer.from_pretrained(checkpoint) {/if} -你可以自由地用这些做任何你想做的事情——向标记器添加标记,训练模型,微调它。一旦您对生成的模型、权重和标记器感到满意,您就可以利用 **push_to_hub()** 方法直接在 **model** 中: +你可以随意进行一些操作——给 tokenizer 添加 tokens 训练模型,微调模型。直到你对最终的模型、权重和 tokenizer 满意。当你对的模最终的模型、权重和 tokenizer 感到满意,你就可以直接在 `model` 对象上调用 `push_to_hub()` 方法: ```py model.push_to_hub("dummy-model") ``` -这将创建新的存储库 **dummy-model** 在您的个人资料中,并用您的模型文件填充它。 -对标记器执行相同的操作,以便所有文件现在都可以在此存储库中使用: +这将在你的账户中创建新的 `dummy-model` 仓库,并将这个模型文件上传上去。 + +对 tokenizer 也需要进行相同的操作: ```py tokenizer.push_to_hub("dummy-model") ``` -如果您属于一个组织,只需指定 **organization** 上传到该组织的命名空间的参数: +如果你属于一个组织,只需指定 `organization` 参数即可将模型上传到该组织的账户下: ```py tokenizer.push_to_hub("dummy-model", organization="huggingface") ``` -如果您希望使用特定的 Hugging Face 令牌,您可以自由地将其指定给 **push_to_hub()** 方法也是: +如果你希望使用特定的 Hugging Face 令牌,你也可以很方便地将其传递给 `push_to_hub()` 方法: ```py tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") ``` -现在前往模型中心找到您新上传的模型:*https://huggingface.co/user-or-organization/dummy-model*。 +现在你就可以前往 [模型中心](https://huggingface.co/user-or-organization/dummy-model) 查看你新上传的模型。 -单击“文件和版本”选项卡,您应该会在以下屏幕截图中看到可见的文件: +单击“文件和版本(Files and versions)”选项卡,你应该能看到以下的文件: {#if fw === 'pt'}
@@ -180,43 +182,38 @@ tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token= -✏️ **试试看**!获取与检查点关联的模型和标记器,并使用该方法将它们上传到您的命名空间中的存储库。在删除之前,请仔细检查该存储库是否正确显示在您的页面上。 +✏️ **试试看** 获取与 `bert-base-cased` checkpoint 相关的模型和 tokenizer 并使用 `push_to_hub()` 方法将它们上传到你账户中的一个仓库。并请仔细检查该仓库在你的页面上显示是否正常。 -如您所见, **push_to_hub()** 方法接受多个参数,从而可以上传到特定的存储库或组织命名空间,或使用不同的 API 令牌。我们建议您查看直接在[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)了解什么是可能的 +如你所见, `push_to_hub()` 方法接受多个参数,从而可以上传到特定的仓库或账户,除此之外还可以使用不同的 API 令牌验证身份。我们建议你直接查看 [🤗 Transformers 文档](https://huggingface.co/transformers/model_sharing) 了解更多的用法。 -这 **push_to_hub()** 方法由[huggingface_hub](https://github.com/huggingface/huggingface_hub)Python 包,为 Hugging Face Hub 提供直接 API。它集成在 🤗 Transformers 和其他几个机器学习库中,例如[allenlp](https://github.com/allenai/allennlp).虽然我们在本章中专注于 🤗 Transformers 集成,但将其集成到您自己的代码或库中很简单。 +位于 [huggingface_hub](https://github.com/huggingface/huggingface_hub) 库里的 `push_to_hub()` 方提供了直接面向 Hugging Face Hub 的 API。它集成在 🤗 Transformers 和其他几个机器学习库中,例如 [allenlp](https://github.com/allenai/allennlp) 。虽然我们在本章中专注于 🤗 Transformers 但将其集成到你自己的代码或库中很简单。 -跳到最后一部分,了解如何将文件上传到新创建的存储库! +到了最后一部分,让我们看看如何将文件上传到你新创建的仓库! -## 使用 huggingface_hub python库 [[使用 huggingface_hub python库]] +## 使用 `huggingface_hub` python 库 [[使用 `huggingface_hub` python 库]] +`huggingface_hub` Python 库是一个包,为模型和数据集中心提供了一系列工具。它提供了简单的方法和类,可以便捷地获取 hub 中仓库的信息以及管理仓库等常见任务。它在 git 的基础上提供了简单的 API,可以在你你的项目中管理这些仓库的内容,以及 hub 的一些功能。 -这 **huggingface_hub** Python 库是一个包,它为模型和数据集中心提供了一组工具。它为常见任务提供了简单的方法和类,例如 -获取有关模型中心上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub -在您的项目和库中。 - -类似于使用 **push_to_hub** API,这将要求您将 API 令牌保存在缓存中。为此,您需要使用 **login** 来自 CLI 的命令,如上一节所述(同样,确保在这些命令前面加上 **!** 字符(如果在 Google Colab 中运行): +与使用 `push_to_hub` API 类似,这需要你将 API 令牌保存在缓存中。为此,你需要在操作系统的命令行使用 CLI 中的 `login` 命令,(如果在 Notebook 中运行,请在这些命令前添加 `!` 字符): ```bash huggingface-cli login -``` - -这 **huggingface_hub** 包提供了几种对我们有用的方法和类。首先,有几种方法可以管理存储库的创建、删除等: +``` `huggingface_hub` 包提供了几个非常便捷方法和类可以帮助我们完成 hub 的管理。首先,有一些方法可以管理仓库的创建、删除等: ```python no-format from huggingface_hub import ( - # User management + # 用户管理 login, logout, whoami, - # Repository creation and management + # 仓库创建和管理 create_repo, delete_repo, update_repo_visibility, - # And some methods to retrieve/change information about the content + # 以及一些检索/更改文件的方法 list_models, list_datasets, list_metrics, @@ -227,10 +224,9 @@ from huggingface_hub import ( ``` -此外,它还提供了非常强大的 **Repository** 用于管理本地存储库的类。我们将在接下来的几节中探讨这些方法和该类,以了解如何利用它们。 - -这 **create_repo** 方法可用于在模型中心上创建新存储库: +此外,它还提供了非常强大的 `Repository` 类来管理本地仓库。我们将在接下来的几节中探讨这些方法和类,以理解如何利用它们。 +可以使用 `create_repo` 方法在 Hub 上创建新的仓库: ```py from huggingface_hub import create_repo @@ -238,7 +234,7 @@ from huggingface_hub import create_repo create_repo("dummy-model") ``` -这将创建存储库 **dummy-model** 在您的命名空间中。如果愿意,您可以使用 **organization** 争论: +这将在你的账户中创建 `dummy-model` 仓库。除此之外你还可以使用 `organization` 参数指定仓库应属于哪个组织: ```py from huggingface_hub import create_repo @@ -246,45 +242,47 @@ from huggingface_hub import create_repo create_repo("dummy-model", organization="huggingface") ``` -这将创建 **dummy-model** 存储库中的 **huggingface** 命名空间,假设您属于该组织。 -其他可能有用的参数是: +这将在 `huggingface` 组织的账户中创建 `dummy-model` 仓库,如果你属于该组织并且有创建仓库的权限。 -- private 以指定存储库是否应对其他人可见。 -- token 如果您想用给定的令牌覆盖存储在缓存中的令牌。 -- repo_type 如果你想创建一个或一个替代一个的而不是模型。接受的值和 datasetspace "dataset""space"。 +其他可能有用的一些参数有: -创建存储库后,我们应该向其中添加文件!跳到下一部分以查看可以处理此问题的三种方法。 +- `private` ,用于指定仓库是否应对其他人可见。 +- `token` ,如果你希望使用特定的身份令牌,而不是缓存中的身份令牌。 +- `repo_type` ,如果你希望创建一个 `dataset` 或 `space` 而不是模型。需要将 `repo_type` 设置为 `"dataset"` 和 `"space"` 。 +创建仓库后,我们应该向其中添加文件!让我们到下一节,看看三种添加文件的方式。 ## 使用网络界面 [[使用网络界面]] -Web 界面提供了直接在 Hub 中管理存储库的工具。使用该界面,您可以轻松创建存储库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 +Web 界面提供了直接在 Hub 中管理仓库的工具。使用该界面,你可以轻松创建仓库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 -要创建新的存储库,请访问[huggingface.co/new](https://huggingface.co/new): +要创建新的仓库,请访问 [huggingface.co/new](https://huggingface.co/new) :
Page showcasing the model used for the creation of a new model repository.
-首先,指定存储库的所有者:这可以是您或您所属的任何组织。如果您选择一个组织,该模型将出现在该组织的页面上,并且该组织的每个成员都可以为存储库做出贡献。 +首先,指定仓库的所有者:这可以是你或你所属的组织。如果你选择一个组织,模型将在会显示在组织的页面上,组织的每个成员可以操作仓库。 -接下来,输入您的模型名称。这也将是存储库的名称。最后,您可以指定您的模型是公开的还是私有的。私人模特要求您拥有付费 Hugging Face 帐户,并允许您将模特隐藏在公众视野之外。 +接下来,输入你的模型名称。同时也是仓库的名称。最后,你可以指定你的模型是公开的还是私有的。私有的模型将被隐藏,对其他的用户不可见。 -创建模型存储库后,您应该看到如下页面: +创建模型仓库后,你应该会看到如下页面:
An empty model page after creating a new repository.
-这是您的模型将被托管的地方。要开始填充它,您可以直接从 Web 界面添加 README 文件。 +这就是你的模型仓库会显示的页面。你可以直接从网页界面添加 README 文件来填写它。
The README file showing the Markdown capabilities.
-README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于构建模型卡。这些对于为您的模型带来价值至关重要,因为它们是您告诉其他人它可以做什么的地方。 +README 文件采用 Markdown 格式 - 你可以根据你的需要自由编写! + +这一章的下一部分将帮助你建立模型卡片。这些模型卡片对于提升模型的价值非常重要,因为它们可以告诉其他人你的模型能做什么。 -如果您查看“文件和版本”选项卡,您会发现那里还没有很多文件——只有自述文件你刚刚创建和.git 属性跟踪大文件的文件。 +如果你希望添加更多文件,点击页面顶部的 “Files and versions(文件和版本)” 标签页。在这里,你可以看到仓库的所有文件以及它们的版本。
The 'Files and versions' tab only shows the .gitattributes and README.md files. @@ -294,15 +292,13 @@ README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于 ## 上传模型文件 [[上传模型文件]] -Hugging Face Hub 上的文件管理系统基于用于常规文件的 git 和 git-lfs(代表[Git Large File Storage](https://git-lfs.github.com/)) 对于较大的文件。 - -在下一节中,我们将介绍将文件上传到 Hub 的三种不同方式:通过 **huggingface_hub** 并通过 git 命令。 +Hugging Face Hub 的文件管理系统基于 git 用于处理常规文件,对于较大的文件则需要使用 git-lfs(详情请参阅 [Git 大文件存储](https://git-lfs.github.com/) ) -### The `upload_file` approach [[The `upload_file` approach]] +在下一节中,我们会讲述怎么以三种不同的方式将文件上传到 Hub: `upload_file` 方法、 `Repository` 类 以及通过 git 命令。 -使用 **upload_file** 不需要在您的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接推送到 🤗 Hub。这种方法的一个限制是它不能处理大于 5GB 的文件。 -如果您的文件大于 5GB,请按照下面详述的另外两种方法进行操作。API 可以按如下方式使用: +### `upload_file` 方法 [[ `upload_file` 方法]] +使用 `upload_file` 不需要在你的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接发送到 Hub。这种方法的一个限制是它不能上传大于 5GB 的文件。如果你的文件大于 5GB,请按照另外两种方法进行操作。 `upload_file` 的使用方法如下: ```py from huggingface_hub import upload_file @@ -313,20 +309,16 @@ upload_file( ) ``` -这将上传文件 **config.json** 可在 **path_to_file** 到存储库的根目录 **config.json** , 到 **dummy-model** 存储库。 -其他可能有用的参数是: +以上的代码会把 `` 位置的 `config.json` 文件上传到 `dummy-model` 仓库的根目录作为模型的 `config.json` 。其他可能有用的参数包括: -- token,如果要通过给定的令牌覆盖缓存中存储的令牌。 -- repo_type, 如果你想要上传一个 `dataset` 或一个 `space` 而不是模型。 接受的值为 `"dataset"` 和 `"space"`. +- `token` ,如果你希望在这里使用特定的身份令牌,而不是缓存中的身份令牌。 +- `repo_type` ,如果你希望创建一个 `dataset` 或 `space` 而不是模型。需要将 `repo_type` 设置为 `"dataset"` 和 `"space"` 。 +### `Repository` 类 [[ `Repository` 类]] `Repository` 类可以使用类似 git 的方式管理一个本地仓库。它解决了使用 git 可能遇到的大部分痛点,提供我们所需的所有功能。 -### The `Repository` class [[The `Repository` class]] +使用这个类需要安装 git 和 git-lfs,所以请确保你已经安装了 git-lfs(如果没有安装,可以查看 [git-lfs 官网](https://git-lfs.github.com/) 的安装指南)并已经在操作系统里配置好了相关功能。 -以类似 git 的方式管理本地存储库。它抽象了 git 可能遇到的大部分痛点,以提供我们需要的所有功能。 - -使用这个类需要安装 git 和 git-lfs,所以确保你已经安装了 git-lfs(参见[here](https://git-lfs.github.com/)安装说明)并在开始之前进行设置。 - -为了开始使用我们刚刚创建的存储库,我们可以通过克隆远程存储库将其初始化到本地文件夹开始: +为了开始使用我们刚刚创建的仓库,我们可以把远程仓库克隆到一个本地文件夹: ```py from huggingface_hub import Repository @@ -334,9 +326,9 @@ from huggingface_hub import Repository repo = Repository("", clone_from="/dummy-model") ``` -这创建了文件夹 **path_to_dummy_folder** 在我们的工作目录中。该文件夹仅包含 **.gitattributes** 文件,因为这是通过实例化存储库时创建的唯一文件 **create_repo**。 +以上代码会在我们当前的工作目录中创建 `` 文件夹。此文件夹里只有 `.gitattributes` 文件,这是使用 `create_repo` 创建仓库时生成的文件。 -从现在开始,我们可以利用几种传统的 git 方法: +从现在开始,我们可以利用许多传统的 git 方法: ```py repo.git_pull() @@ -346,24 +338,24 @@ repo.git_push() repo.git_tag() ``` -另外!我们建议您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有关所有可用方法的概述。 +此外!我们建议你查看 [huggingface](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) 的 `Repository` 文档,了解一下所有可用方法。 -目前,我们有一个模型和一个标记器,我们希望将其推送到模型中心。我们已经成功克隆了存储库,因此我们可以将文件保存在该存储库中。 +现在,我们有了一个模型和一个 tokenizer 我们希望将其推送到 hub。我们已经成功克隆了仓库,因此我们可以将文件保存到在该仓库的文件夹中。 -我们首先通过拉取最新更改来确保我们的本地克隆是最新的: +首先,我们通过拉取最新的更改,确保我们的本地克隆是最新的: ```py repo.git_pull() ``` -完成后,我们保存模型和标记器文件: +完成后,我们保存模型和 tokenizer 文件: ```py model.save_pretrained("") tokenizer.save_pretrained("") -``` +``` -这 **path_to_dummy_folder** 现在包含所有模型和标记器文件。我们遵循通常的 git 工作流程,将文件添加到暂存区,提交它们并将它们推送到模型中心: +`` 现在已经存储了模型和 tokenizer 文件。我们遵循常规的 git 工作流程,将文件添加到暂存区,提交它们并推送它们到 hub: ```py repo.git_add() @@ -371,15 +363,15 @@ repo.git_commit("Add model and tokenizer files") repo.git_push() ``` -恭喜!您刚刚将第一个文件推送到hub上。 +恭喜你!你刚刚在 hub 上推送了你的第一个文件。 -### The git-based approach [[The git-based approach]] +### 基于 git 的方法 [[基于 git 的方法]] -这是上传文件的非常简单的方法:我们将直接使用 git 和 git-lfs 来完成。大多数困难都被以前的方法抽象掉了,但是下面的方法有一些警告,所以我们将遵循一个更复杂的用例。 +这是上传文件的最基础方法:我们将直接使用 git 和 git-lfs 进行操作,相比于之前的方法难度更大同时也更加灵活。在前面的方法大部分的难点都已经被封装好的库解决,但是接下来的方法需要手动来解决它们,所以我们会尝试一个更复杂的使用案例。 -使用这个类需要安装 git 和 git-lfs,所以请确保你有[git-lfs](https://git-lfs.github.com/)安装(请参阅此处了解安装说明)并在开始之前进行设置。 +使用这个方法需要安装 git 和 git-lfs,所以请确保你已经安装了 git-lfs(如果没有安装,可以参见 [git-lfs 官网](https://git-lfs.github.com/) 的安装指南)并已经在操作系统里配置好了相关功能。 -首先从初始化 git-lfs 开始: +首先,初始化 git-lfs: ```bash git lfs install @@ -390,19 +382,19 @@ Updated git hooks. Git LFS initialized. ``` -完成后,第一步是克隆您的模型存储库: +完成后,接下来需要克隆你的模型仓库: ```bash git clone https://huggingface.co// ``` -我的用户名是 **lysandre** 我使用了模型名称 **dummy** ,所以对我来说,命令最终如下所示: +我的用户名是 `lysandre` 我使用的模型的名称是 `dummy` ,所以对我来说,命令最终如下所示: ``` git clone https://huggingface.co/lysandre/dummy ``` -我现在有一个名为的文件夹假在我的工作目录中。我能 **cd** 进入文件夹并查看内容: +我现在在工作目录中多了一个名为 dummy 的文件夹。我可以 cd 到这个文件夹,看看其中的内容: ```bash cd dummy && ls @@ -412,11 +404,11 @@ cd dummy && ls README.md ``` -如果您刚刚使用 Hugging Face Hub 创建了您的存储库 **create_repo** 方法,这个文件夹应该只包含一个隐藏的 **.gitattributes** 文件。如果您按照上一节中的说明使用 Web 界面创建存储库,则该文件夹应包含一个自述文件文件旁边的隐藏 **.gitattributes** 文件,如图所示。 +如果你刚刚用的是 Hugging Face Hub 的 `create_repo` 方法创建的仓库,这个文件夹应该只包含一个隐藏的 .gitattributes 文件。如果你使用的是网页界面创建了一个仓库,那么文件夹应该只包含一个 README.md 文件和一个隐藏的 .gitattributes 文件。 -添加一个常规大小的文件,例如配置文件、词汇文件,或者基本上任何几兆字节以下的文件,就像在任何基于 git 的系统中所做的一样。但是,更大的文件必须通过 git-lfs 注册才能将它们推送到Hugging Face。 +较小的文件,如配置文件、词汇表文件,或者基本上任何几 MB 以下的文件,操作的方法就像我们平时使用 git 一样。然而,更大的文件必须通过 git-lfs 注册才能推送到 huggingface.co。 -让我们回到 Python 来生成我们想要提交到我们的虚拟存储库的模型和标记器: +让我们回到 Python,生成一些我们希望提交到 dummy 仓库的模型和 tokenizer {#if fw === 'pt'} ```py @@ -427,7 +419,7 @@ checkpoint = "camembert-base" model = AutoModelForMaskedLM.from_pretrained(checkpoint) tokenizer = AutoTokenizer.from_pretrained(checkpoint) -# Do whatever with the model, train it, fine-tune it... +# 对模型进行一些操作,训练、微调... model.save_pretrained("") tokenizer.save_pretrained("") @@ -441,14 +433,14 @@ checkpoint = "camembert-base" model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) tokenizer = AutoTokenizer.from_pretrained(checkpoint) -# Do whatever with the model, train it, fine-tune it... +# 对模型进行一些操作,训练、微调... model.save_pretrained("") tokenizer.save_pretrained("") ``` {/if} -现在我们已经保存了一些模型和标记器工件,让我们再看看假文件夹: +现在我们已经保存了模型和 tokenizer 让我们再看一下 dummy 文件夹: ```bash ls @@ -459,22 +451,22 @@ ls config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json ``` -If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB. +如果你尝试查看文件大小(例如,使用 `ls -lh` ),你应该看到模型状态字典文件(pytorch_model.bin)是唯一的异常的文件,超过了 400 MB。 {:else} ```bash config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json ``` -如果您查看文件大小(例如, **ls -lh** ),您应该会看到模型状态 dict 文件 (pytorch_model.bin) 是唯一的异常值,超过 400 MB。 +如果你尝试查看文件大小(例如, `ls -lh` ),你应该会看到模型状态字典文件 (t5_model.h5) 唯一的异常的文件,超过了 400 MB。 {/if} -✏️ 从 web 界面创建存储库时,*.gitattributes* 文件会自动设置为将具有某些扩展名的文件,例如 *.bin* 和 *.h5* 视为大文件,git-lfs 会对其进行跟踪您无需进行必要的设置。 +✏️ 当通过网页界面创建仓库时, `.gitattributes` 文件会自动将某些扩展名(如 `.bin` 和 `.h5` )的文件视为大文件,你无需对 git-lfs 进行任何设置即可跟踪它们。 -我们现在可以继续进行,就像我们通常使用传统 Git 存储库一样。我们可以使用以下命令将所有文件添加到 Git 的暂存环境中 **git add** 命令: +我们现在可以继续上传,就像我们使用传统 Git 仓库一样。我们可以使用以下命令将所有文件添加到 Git 的暂存环境中 `git add` 命令: ```bash git add . @@ -518,7 +510,7 @@ Changes to be committed: ``` {/if} -同样,我们可以确保 git-lfs 使用其跟踪正确的文件 **status** 命令: +同样,我们可以使用 `status` 命令查看 git-lfs 正在跟踪的文件: ```bash git lfs status @@ -544,7 +536,7 @@ Objects not staged for commit: ``` -我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*pytorch_model.bin* 和 *sentencepiece.bpe.model*。 +我们可以看到所有文件都使用 `Git` 作为处理程序,除了 `pytorch_model.bin` 和 `sentencepiece.bpe.model` 它们的处理程序是 `LFS` 。 {:else} ```bash @@ -566,11 +558,11 @@ Objects not staged for commit: ``` -我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*t5_model.h5*。 +我们可以看到所有文件都使用 `Git` 作为处理程序,除了 `t5_model.h5` ,它的处理程序是 `LFS` 。 {/if} -让我们继续最后的步骤,提交并推送到Hugging Face远程仓库: +让我们继续进行最后的步骤,提交并推送到 huggingface.co 远程仓库: ```bash git commit -m "First model version" @@ -601,7 +593,7 @@ git commit -m "First model version" ``` {/if} -推送可能需要一些时间,具体取决于您的互联网连接速度和文件大小: +推送可能需要一些时间,具体取决于你的网络速度以及你的文件大小: ```bash git push @@ -620,13 +612,13 @@ To https://huggingface.co/lysandre/dummy ``` {#if fw === 'pt'} -If we take a look at the model repository when this is finished, we can see all the recently added files: +当这些都完成时,如果我们查看模型仓库,我们可以看到所有最近添加的文件:
The 'Files and versions' tab now contains all the recently uploaded files.
-UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: +在Huggingface 的网页上你可以查看模型文件和提交,并查看每次提交引入的差异:
The diff introduced by the recent commit. @@ -634,13 +626,13 @@ UI 允许您浏览模型文件和提交,并查看每个提交引入的差异 {:else} -如果我们在完成后查看模型存储库,我们可以看到所有最近添加的文件: +当这些都完成时,如果我们查看模型仓库,我们可以看到所有最近添加的文件:
The 'Files and versions' tab now contains all the recently uploaded files.
-UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: +在Huggingface 的网页上你可以查看模型文件和提交,并查看每次提交引入的差异:
The diff introduced by the recent commit. diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx index e844ac18a..3f78af83e 100644 --- a/chapters/zh-CN/chapter4/4.mdx +++ b/chapters/zh-CN/chapter4/4.mdx @@ -7,11 +7,11 @@ 模型卡片是一个配置文件,可以说与模型存储库中的模型和 tokenizer 文件一样重要。它包含了模型的核心定义,确保了社区成员可以复现模型的结果,并提供一个其他成员可以在这个模型基础上构建他们的组件的平台。 -记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——确保模型存在和目前的限制、偏差可以识别和理解。 +记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——模型存的局限性、偏见以及模型有效或无效的使用场景。 -因此,创建清晰定义模型的模型卡片是非常重要的一步。在这里,我们提供了一些可以帮助您解决此问题的方法。创建模型卡片是通过您之前看到的 Markdown 文件:README.md 。 +因此,创建一个清晰定义你的模型的模型卡片是非常重要的一步。在这里,我们提供了一些可以帮助你创建模型卡片的建议。创建模型卡片的内容保存在你之前看到的 README.md 文件中,这是一个 Markdown 文件。 -“模型卡片”的概念源于谷歌的一个研究方向, Margaret Mitchell 等人在论文[“Model Cards for Model Reporting”](https://arxiv.org/abs/1810.03993)中首次提出,此处包含的许多信息均基于该论文,我们建议您查看这篇论文以了解为什么模型卡片在重视可重复性、可重用性和公平性的时候中如此重要。 +“模型卡片”的概念源于谷歌的一个研究方向,Margaret Mitchell 等人在论文 [“模型卡片用于模型报告”](https://arxiv.org/abs/1810.03993) 中首次提出,这里许多内容均基于该论文,我们建议你查看这篇论文以了解为什么模型卡片在实现可复现性、可重用性和公平性的中如此重要。 模型卡通常以非常简短的概述开始,说明模型的用途,然后是模型卡片需要的其他信息: @@ -25,53 +25,53 @@ 让我们来看看每个部分应该包含什么。 -### 模型描述: [[模型描述:]] +### 模型描述:[[模型描述:]] 提供了有关模型的基本详细信息。这包括架构、版本、如果它是在论文中介绍的,是否有原始的实现可用?作者以及有关模型的一般信息、任何版权都应归于此处。这一部分还可以提及有关训练程序、参数和重要免责声明的一般信息。 -### 预期用途和限制: [[预期用途和限制:]] +### 预期用途和限制:[[预期用途和限制:]] 在此描述模型可以适用的例子,包括可以应用它的语言、领域。模型卡的这一部分还可以记录已知超出模型范围的区域,或者可能表现不佳的区域。 -### 使用方法: [[使用方法:]] +### 使用方法:[[使用方法:]] -此部分应包括一些有关如何使用模型的示例。这可以展示使用 **pipeline()** 函数、模型和标记器类的使用以及其他任何您认为可能有帮助的代码。 +此部分应包括一些有关如何使用模型的示例。这可以展示使用 `pipeline()` 函数、模型和 tokenizer 的使用以及其他任何你认为可能有帮助的代码。 -### 训练数据: [[训练数据:]] +### 训练数据:[[训练数据:]] 这部分应该指出模型是在哪个数据集上训练的。也欢迎对数据集进行简要描述。 -### 训练过程: [[训练过程:]] +### 训练过程:[[训练过程:]] -此部分中,您应该描述从再现性角度来看有用的训练的所有相关方面。这包括对数据进行的任何预处理和后处理,以及模型训练的批量数、批量大小、学习率等细节。 +此部分中,你应该描述从再现性角度来看有用的训练的所有相关方面。这包括对数据进行的任何预处理和后处理,以及模型训练的批量数、批量大小、学习率等细节。 -### 变量和指标: [[变量和指标:]] +### 变量和指标:[[变量和指标:]] -在这里,您应该描述您用于评估的指标,以及您测量的不同因素。提及使用了哪些指标、在哪个数据集上以及哪个数据集部分,可以轻松地将您的模型的性能与其他模型的性能进行比较。 +在这里,你应该描述你用于评估的指标,以及你测量的不同因素。提及使用了哪些指标、在哪个数据集上以及哪个数据集部分,可以轻松地将你的模型的性能与其他模型的性能进行比较。 -### 评价结果: [[评价结果:]] +### 评估结果:[[评估结果:]] 这些应该提前在前面的部分告知,例如预期的使用效果和示例。最后,提供模型在评估数据集上的表现的指示。如果模型使用决策阈值,要么提供评估中使用的决策阈值,要么提供在不同阈值下针对预期用途进行评估的详细信息。 -## 例子 [[例子]] +## 示例 [[示例]] -查看以下几个精心制作的模型卡的例子: +请查看以下几个精心制作的模型卡的示例: -* [bert-base-cased](https://huggingface.co/bert-base-cased) -* [gpt2](https://huggingface.co/gpt2) -* [distilbert](https://huggingface.co/distilbert-base-uncased) +* [bert-base-cased](https://huggingface.co/bert-base-cased) +* [gpt2](https://huggingface.co/gpt2) +* [distilbert](https://huggingface.co/distilbert-base-uncased) -更多来自于不同组织和公司的示例可以在[这里](https://github.com/huggingface/model_card/blob/master/examples.md)查阅. +更多来自于不同组织和公司的示例可以在 [模型卡片示例](https://github.com/huggingface/model_card/blob/master/examples.md) 中查阅. -## 提示 [[提示]] +## 注意事项 [[注意事项]] -发布模型时不需要模型卡,制作一个模型时不需要包含上述所有部分。但是,模型的文档会使未来的用户受益,因此我们建议您尽自己的知识和能力填写尽可能多的部分。 +发布模型时,并非必须有模型卡片,制作一个模型时不需要包含上述所有部分。但是,模型的文档会使未来的用户受益,因此我们建议你尽你所知和能力所及地尽可能填写尽可能多的部分。 ## 模型卡片元数据 [[模型卡片元数据]] -如果您对 Hugging Face Hub 进行了一些探索,您应该已经看到某些模型属于某些类别:您可以按任务、语言、库等对其进行过滤。模型所属的类别来自于您在模型卡片标题中添加的元数据。 +如果你对 Hugging Face Hub 进行了一些探索,你应该已经看到某些模型属于某些类别:你可以按任务、语言、库等对其进行筛选。模型所属的类别来自于你在模型卡片头部中添加的元数据。 -例如,如果你看一下[`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md),您应该在模型卡标题中看到以下几行: +例如,如果你看一下 [`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md) ,你应该在模型卡头部中看到以下几行: ``` --- @@ -82,6 +82,6 @@ datasets: --- ``` -该元数据由 Hugging Face Hub 解析,然后将这个模型识别为法语模型,拥有 MIT 许可证,在 Oscar 数据集上训练。 +该元数据由 Hugging Face Hub 解析,然后将这个模型为法语模型,拥有 MIT 许可证,在 Oscar 数据集上训练。 -允许的指定语言、许可证、标签、数据集、指标以及模型在训练时获得的评估结果在[全部模型卡片的规格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md)可以查阅。 \ No newline at end of file +可以在 [全部模型卡片的规格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md) 查阅支持的语言、许可证、标签、数据集、指标以及模型在训练时获得的评估结果。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/5.mdx b/chapters/zh-CN/chapter4/5.mdx index 5c3b9708d..9218fc5d9 100644 --- a/chapters/zh-CN/chapter4/5.mdx +++ b/chapters/zh-CN/chapter4/5.mdx @@ -1,12 +1,12 @@ -# Part 1 完结! [[Part 1 完结!]] +# Part 1 完结![[Part 1 完结!]] -这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,[点击这里](https://huggingface.co/blog/course-launch-event)查看更多信息. +这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,更多信息请 [点击这里](https://huggingface.co/blog/course-launch-event) 。 -您现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保您掌握了第一部分的内容,您应该针对您感兴趣的想法进行尝试(不一定是英语)!一旦你完成,您可以在[Hugging Face 社区](https://discuss.huggingface.co/)的[这个话题](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的项目。 +你现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保你掌握了第一部分的内容,你应该针对你感兴趣的想法进行尝试(不一定是英语)!一旦你完成,你可以在 [Hugging Face 社区](https://discuss.huggingface.co/) 的 [这个话题](https://discuss.huggingface.co/t/share-your-projects/6803) 分享你的项目。 -我们迫不及待地想看看您将用它构建什么! \ No newline at end of file +我们迫不及待地想看到你会用这些内容做出什么! diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx index 8308af695..218ba78b9 100644 --- a/chapters/zh-CN/chapter4/6.mdx +++ b/chapters/zh-CN/chapter4/6.mdx @@ -10,31 +10,32 @@ /> 让我们测试一下你在本章所学的知识! - -### 1.Hub上的模型有什么限制? + +### 1.往Hub 上传模型有什么限制? + -### 2.如何管理Hub上的模型? +### 2.如何管理 Hub 上的模型? -### 3.你能使用Hugging Face Hub网页接口做什么? +### 3.你能使用 Hugging Face Hub 网页接口做什么? -### 4.模型卡是什么? +### 4.什么是模型卡片? -### 5.哪些🤗 Transformers 库的对象可以直接在 Hub 上通过`push_to_Hub()`共享? +### 5.哪些🤗 Transformers 库的对象可以直接通过 `push_to_hub()` 分享到 Hub? {#if fw === 'pt'} push_to_hub方法,使用该方法将把所有标记器文件(词汇表、标记器的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", + text: " tokenizer ", + explain: "正确!所有 tokenizer 都有 `push_to_hub` 方法,使用该方法将把 tokenizer 的全部文件(词汇表、 tokenizer 的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", correct: true }, { - text: "模型配置", - explain: "对!所有模型配置都有push_to_hub方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", + text: "模型的 Config 对象", + explain: "对!所有模型的 Config 对象都有 `push_to_hub` 方法,使用这个方法可以将它们推送到给定的存储库。你还有其他的答案吗?", correct: true }, { - text: "一个模型", - explain: "正确! 所有模型都有push_to_hub方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", + text: "Model 类", + explain: "正确!所有的 Model 类都有 `push_to_hub` 方法,使用它会将它们及其配置文件推送到给定的存储库。不过,还有其他的正确答案", correct: true }, { - text: "Trainer", - explain: "没错————Trainer也实现了push_to_hub方法,并且使用它将模型、配置、标记器和模型卡草稿上传到给定的存储器。试试其他答案!", + text: "Trainer 类", + explain: "没错 —— `Trainer` 也实现了 `push_to_hub` 方法,并且使用它可以将模型、配置、 tokenizer 和模型卡片上传到指定的仓库。试试其他答案!", correct: true } ]} @@ -132,89 +133,90 @@ push_to_hub方法,使用该方法将把所有标记器文件(词汇表、标记器的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", + text: " tokenizer ", + explain: "正确!所有 tokenizer 都有 `push_to_hub` 方法,使用该方法将把所有 tokenizer 文件(词汇表、 tokenizer 的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", correct: true }, { - text: "模型配置", - explain: "对!所有模型配置都有push_to_hub方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", + text: "模型的 Config 对象", + explain: "对!所有模型的 Config 对象都有 `push_to_hub` 方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", correct: true }, { - text: "一个模型", - explain: "正确! 所有模型都有push_to_hub方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", + text: "Model 类", + explain: "正确!所有Model 类都有 `push_to_hub` 方法,使用它会将它们及其配置文件推送到给定的存储库。不过,还有其他可以分享到hub。", correct: true }, { - text: "以上都有专用的回调函数", - explain: "正确————在训练期间,PushToHubCallback会定期将所有这些对象发送到存储器。", + text: "所有以上的对象都可以通过专用的回调方法共享到 hub", + explain: "正确 —— 在训练期间, `PushToHubCallback` 会定期将所有这些对象发送到 hub。", correct: true } ]} /> {/if} -### 6.当使用`push_to_hub()`方法或 CLI 工具时,第一步是什么? +### 6.当使用 `push_to_hub()` 方法或 CLI 工具时,第一步是什么? -### 7.您正在使用一个模型和一个标记器————如何将它们上传到 Hub? +### 7.你正在使用一个模型和一个 tokenizer ——如何将它们上传到 Hub? + huggingface_hub实用程序中。", - explain: "模型和标记器已经受益于huggingface_hub 实用程序: 不需要额外的包装!" + text: "在 Python 运行时中,使用 `huggingface_hub` 中的方法进行封装。", + explain: "模型和 tokenizer 已经使用 `huggingface_hub` 封装过了:不需要额外的封装!" }, { - text: "将它们保存到磁盘并调用 transformers-cli upload-model", - explain: "命令 upload-model 不存在。" + text: "将它们保存到磁盘并调用 `transformers-cli upload-model` ", + explain: "命令 `upload-model` 不存在。" } ]} /> -### 8.您可以使用`Repository`类执行哪些 git 操作? +### 8.你可以使用 `Repository` 类执行哪些 git 操作? git_commit() 方法就是为此而存在的。", + text: "提交(commit)", + explain: "正确, `git_commit()` 方法就是为此而存在的。", correct: true }, { - text: "拉取", - explain: "这就是 git_pull() 方法的目的。", + text: "拉取(pull)", + explain: "这就是 `git_pull()` 方法的功能。", correct: true }, { - text: "推送", - explain: "方法 git_push() 可以做到这一点。", + text: "推送(push)", + explain: "方法 `git_push()` 可以做到这一点。", correct: true }, { - text: "合并", - explain: "不,这个操作在这个 API 中是不可能的。" + text: "合并(merge)", + explain: "不,这个操作在这个 API 中是无法实现的。" } ]} /> diff --git a/chapters/zh-CN/chapter5/1.mdx b/chapters/zh-CN/chapter5/1.mdx index c13d18b91..d626e75a2 100644 --- a/chapters/zh-CN/chapter5/1.mdx +++ b/chapters/zh-CN/chapter5/1.mdx @@ -5,18 +5,17 @@ classNames="absolute z-10 right-0 top-0" /> -在[第三章](/course/chapter3)第一次体验了🤗Datasets 库,并发现在微调模型时有三个主要步骤: +我们在 [第三章](/course/chapter3) 第一次体验了🤗 Datasets 库,了解到微调模型主要有三个步骤: -1. 从hugs Face Hub加载一个数据集。 -2. 使用Dataset.map()对数据进行预处理。 -3. 加载和计算指标(特征)。 +1. 从 Hugging Face Hub 加载数据集。 +2. 使用 `Dataset.map()` 预处理数据。 +3. 加载和计算指标(特征)。 -但这只是🤗 Datasets的表面功能而已!在本章中,我们将深入了解这个库。在此过程中,我们将找到以下问题的答案: - -* 当数据集不在hub上时,您该怎么做? -* 如何对数据集进行切片?(如果你真正的特别需要使用pandas的时候该怎么办?) -* 当你的数据集很大,会撑爆你笔记本电脑的RAM时,你会怎么做? -* “内存映射”和Apache Arrow到底是什么? +但这仅仅触及了🤗 Datasets 库能做的事情的冰山一角!在本章,我们将深入探索这个库。一路上,我们会找到以下问题的答案: +* 当你的数据集不在 Hub 上时,你应该怎么做? +* 你如何切分和操作数据集?(如果你非常需要使用 Pandas,该如何处理?) +* 当你的数据集非常大,会撑爆你笔记本电脑的 RAM 时,你应该怎么办? +* 什么是“内存映射”和 “Apache Arrow”? * 如何创建自己的数据集并将其推送到中心? -您在这里学到的技术将为您在[第6章](/course/chapter6)和[第7章](/course/chapter7)中的高级标记化和微调任务做好准备——所以,喝杯咖啡,让我们开始吧! \ No newline at end of file +你在这里学到的技术将为你在 [第六章](/course/chapter6) 和 [第七章](/course/chapter7) 中的高级 tokenization 和微调任务做好准备——所以,来杯咖啡,让我们开始吧! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index 555e35f70..478e457b5 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -1,4 +1,4 @@ -# 如果我的数据集不在 Hub 上怎么办? [[如果我的数据集不在 Hub 上怎么办?]] +# 如果我的数据集不在 Hub 上怎么办?[[如果我的数据集不在 Hub 上怎么办?]] -你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下载数据集, 但你经常会发现自己正在处理存储在笔记本电脑或远程服务器上的数据。在本节中,我们将向您展示如何使用 🤗 Datasets来加载 Hugging Face Hub 上不可用的数据集。 +你已经知道如何使用 [Hugging Face Hub](https://huggingface.co/datasets) 中的数据集,但你往往会发现自己需要处理在自己的笔记本电脑或者网络上的数据集。在本节中,我们将展示如何使用🤗 Datasets 加载不在 Hugging Face Hub 中的数据集。 ## 使用本地和远程数据集 [[使用本地和远程数据集]] -🤗 Datasets 提供了加载脚本来加载本地和远程数据集。它支持几种常见的数据格式,例如: +🤗 Datasets 提供了加载本地和远程数据集的方法。它支持几种常见的数据格式,例如: -| Data format | Loading script | Example | +| 数据格式 | 类型参数 | 加载的指令 | | :----------------: | :------------: | :-----------------------------------------------------: | -| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | -| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | -| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | -如表所示, 对于每种数据格式, 我们只需要使用 `load_dataset()` 函数, 使用 `data_files` 指定一个或多个文件的路径的参数。 让我们从本地文件加载数据集开始;稍后我们将看到如何对远程文件执行相同的操作。 +如表所示,对于每种数据格式,我们只需要在 `load_dataset()` 函数中指定数据的类型,并使用 `data_files` 指定一个或多个文件的路径的参数。首先,我们从加载本地文件的数据集开始;稍后,我们将看到如何使用远程文件做同样的事情。 ## 加载本地数据集 [[加载本地数据集]] -对于这个例子,我们将使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 这是一个大规模的意大利语问答数据集。 +在这个例子中,我们将使用 [SQuAD-it 数据集](https://github.com/crux82/squad-it/) ,这是一个用于意大利语问答的大规模数据集。 -训练和测试都托管在 GitHub 上, 因此我们可以通过`wget`命令非常简单地下载它们: +训练集和测试集都托管在 GitHub 上,因此我们可以通过 `wget` 命令非常轻易地下载它们: ```python !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz ``` -这将下载两个名为*SQuAD_it-train.json.gz* 和 *SQuAD_it-test.json.gz*的压缩文件, 我们可以用Linux的解压命令 `gzip`: +这将下载两个名为 `SQuAD_it-train.json.gz` 和 `SQuAD_it-test.json.gz` 的压缩文件,我们可以用 Linux 的 `gzip` 命令解压他们: ```python !gzip -dkv SQuAD_it-*.json.gz @@ -46,15 +46,15 @@ SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json ``` -我们可以看到压缩文件已经被替换为SQuAD_it-train.json和SQuAD_it-test.json,并且数据以 JSON 格式存储。 +我们可以看到压缩文件已经被替换为 `SQuAD_it-train.json` 和 `SQuAD_it-test.json` ,并且数据以 JSON 格式存储。 -✎ 如果你想知道为什么上面的shell命令中哟与一个字符`!`,那是因为我们是在 Jupyter notebook 中运行它们。如果您想在终端中下载和解压缩数据集,只需删除前缀!即可。 +✏️ 如果你想知道为什么上面的 shell 命令中有一个 `!` ,那是因为我们现在是在 Jupyter notebook 中运行它们。如果你想在命令行中下载和解压缩数据集,只需删除前缀 `!` 即可。 -使用`load_dataset()`函数来加载JSON文件, 我们只需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON 行(行分隔的 JSON)。像许多问答数据集一样, SQuAD-it 使用嵌套格式,所有文本都存储在 `data`文件中。这意味着我们可以通过指定参数`field`来加载数据集,如下所示: +当我们使用 `load_dataset()` 函数来加载 JSON 文件时,我们需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON Lines(每一行都是一个 JSON)。像许多问答数据集一样,SQuAD-it 使用的是嵌套字典,所有文本都存储在 `data` 字段中。这意味着我们可以通过使用参数 `field` 来加载数据集,如下所示: ```py from datasets import load_dataset @@ -62,7 +62,7 @@ from datasets import load_dataset squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") ``` -默认情况下, 加载本地文件会创建一个带有`train`的`DatasetDict` 对象。 我们可以通过 `squad_it_dataset`查看: +默认情况下,加载本地文件会创建一个带有 `train` 标签的 `DatasetDict` 对象。我们可以在这里查看一下 `squad_it_dataset` 对象: ```py squad_it_dataset @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -这向我们显示了与训练集相关联的行数和列名。我们可以通过索引到 `train` 查看示例,如下所示: +输出了与训练集的行数和列名。我们可以使用 `train` 标签来查看数据集中的一个示例,如下所示: ```py squad_it_dataset["train"][0] @@ -103,7 +103,7 @@ squad_it_dataset["train"][0] } ``` -很好, 我们已经加载了我们的第一个本地数据集! 但是, 虽然这对训练集有效, 但是我们真正想要的是包括 `train` 和 `test` 的 `DatasetDict` 对象。这样的话就可以使用 `Dataset.map()` 函数同时处理训练集和测试集。 为此, 我们提供参数`data_files`的字典,将每个分割名称映射到与该分割相关联的文件: +很好,我们已经加载了我们的第一个本地数据集!但是,也仅仅加载了训练集,我们真正想要的是包含 `train` 和 `test` 的 `DatasetDict` 对象。这样的话就可以使用 `Dataset.map()` 函数同时处理训练集和测试集。为此,我们向 `data_files` 参数输入一个字典,将数据集的标签名映射到相关联的文件: ```py data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} @@ -124,28 +124,28 @@ DatasetDict({ }) ``` -这正是我们想要的。现在, 现在,我们可以应用各种预处理技术来清理数据、标记评论等。 +这正是我们想要的。现在,我们可以使用各种预处理技术来清洗数据、tokenize 评论等等。 - + -`load_dataset()`函数的`data_files`参数非常灵活并且可以是单个文件路径、文件路径列表或将分割后的名称映射到文件路径的字典。您还可以根据Unix shell使用的规则对与指定模式匹配的文件进行全局定位(例如,您可以通过设置'data_files=“*.JSON”'将目录中的所有JSON文件作为单个拆分进行全局定位)。有关更多详细信息,请参阅🤗Datasets 文档。 +`load_dataset()` 函数的 `data_files` 参数非常灵活:可以是单个文件路径、文件路径列表或者是标签映射到文件路径的字典。你还可以根据 Unix shell 的规则,对符合指定模式的文件进行批量匹配(例如,你可以通过设置 `data_files="*.JSON"` 匹配目录中所有的 JSON 文件)。有关`load_dataset()`更多详细信息,请参阅 [🤗Datasets 文档](https://huggingface.co/docs/datasets/v2.12.0/en/loading#local-and-remote-files) 。 -🤗 Datasets实际上支持输入文件的自动解压,所以我们可以跳过使用`gzip`,直接设置 `data_files`参数传递压缩文件: +🤗 Datasets 实际上支持自动解压输入文件,所以我们可以跳过使用 `gzip` ,直接将 `data_files` 参数设置为压缩文件: ```py data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -如果您不想手动解压缩许多 GZIP 文件,这会很有用。自动解压也适用于其他常见格式,如 ZIP 和 TAR,因此您只需将 `data_files` 设置为压缩文件所在的路径,你就可以开始了! +如果你不想手动解压缩许多 GZIP 文件,这会很有用。自动解压也支持于其他常见格式,如 ZIP 和 TAR,因此你只需将 `data_files` 设置为压缩文件所在的路径,接下来就交给🤗 Datasets 吧! -现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看加载远程文件。 +现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看如何加载远程文件。 ## 加载远程数据集 [[加载远程数据集]] -如果你在公司担任数据研究员或编码员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们没有提供本地文件的路径, 而是将`load_dataset()`的`data_files`参数指向存储远程文件的一个或多个URL。例如, 对于托管在 GitHub 上的 SQuAD-it 数据集, 我们可以将 `data_files` 指向 _SQuAD_it-*.json.gz_ 的网址,如下所示: +如果你在公司担任数据研究员或编程员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们只需要将 `load_dataset()` 的 `data_files` 参数指向存储远程文件的一个或多个 URL。例如,对于托管在 GitHub 上的 SQuAD-it 数据集,我们可以将 `data_files` 设置为指向 `SQuAD_it-*.json.gz` 的网址,如下所示: ```py url = "https://github.com/crux82/squad-it/raw/master/" @@ -156,12 +156,11 @@ data_files = { squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -这将返回和上面的本地例子相同的 `DatasetDict` 对象, 但省去了我们手动下载和解压 _SQuAD_it-*.json.gz_ 文件的步骤。这是我们对加载未托管在Hugging Face Hub的数据集的各种方法的总结。既然我们已经有了一个可以使用的数据集,让我们开始大展身手吧! +这将返回和上面的本地例子相同的 `DatasetDict` 对象,但省去了我们手动下载和解压 `SQuAD_it-*.json.gz` 文件的步骤。这是我们对加载未托管在 Hugging Face Hub 的数据集的各种方法的总结。既然我们已经有了一个可以使用的数据集,让我们开始大展身手吧! -✏️ **试试看!** 选择托管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一个数据集并尝试使用上述技术在本地和远程加载它。另外,可以尝试加载CSV或者文本格式存储的数据集(有关这些格式的更多信息,请参阅[文档](https://huggingface.co/docs/datasets/loading#local-and-remote-files))。 +✏️ **试试看!** 选择托管在 GitHub 或 [UCI 机器学习仓库](https://archive.ics.uci.edu/ml/index.php) 上的另一个数据集并尝试使用上述技术在本地和远程加载它。另外,可以尝试加载 CSV 或者文本格式存储的数据集(有关这些格式的更多信息,请参阅 [文档](https://huggingface.co/docs/datasets/loading#local-and-remote-files) )。 - diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx index 239780da7..0feb10d03 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -1,4 +1,4 @@ -# 是时候来学一下切片了 [[是时候来学一下切片了]] +# 分割和整理数据 [[分割和整理数据]] -大多数情况下,您使用的数据都需根据模型所要求的输入进行清洗。在本节中,我们将探索 🤗 Datasets 提供的用于数据集清洗的各种功能。 +大多数情况下,你处理的数据并不能直接用于训练模型。在本节中,我们将探索🤗 Datasets 提供的各种功能,用于清洗你的数据集。 -## 切片与切分我们的数据 [[切片与切分我们的数据]] +## 分割和整理我们的数据 [[分割和整理我们的数据]] -与 Pandas 类似,🤗 Datasets 提供了几个函数来操作 **Dataset** 和 **DatasetDict** 对象。我们在[第三章](/course/chapter3)已经遇到了 **Dataset.map()** 方法,在本节中,我们将探索我们可以使用的其他功能。 +与 Pandas 类似,🤗 Datasets 提供了多个函数来操作 `Dataset` 和 `DatasetDict` 对象。我们在 [第三章](/course/chapter3) 已经遇到了 `Dataset.map()` 方法,在本节中,我们将探索一些其他可用的函数。 -对于这个例子,我们将使用托管在[加州大学欧文分校机器学习存储库](https://archive.ics.uci.edu/ml/index.php)的[药物审查数据集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29),其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评级。 +在本例中,我们将使用托管在 [加州大学欧文分校机器学习仓库](https://archive.ics.uci.edu/ml/index.php) 的 [药物审查数据集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) ,其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评价。 -首先我们需要下载并提取数据,这可以通过 **wget** 和 **unzip** 命令: +首先我们需要下载并解压数据,可以通过 `wget` 和 `unzip` 命令: ```py !wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" !unzip drugsCom_raw.zip ``` -由于 TSV 只是使用制表符而不是逗号作为分隔符的 CSV 变体,我们可以使用加载**csv**文件的**load_dataset()**函数并指定分隔符 示例如下: +由于 TSV 仅仅是 CSV 的一个变体,它使用制表符而不是逗号作为分隔符,我们可以使用加载 `csv` 文件的 `load_dataset()` 函数并指定分隔符,来加载这些文件: ```py from datasets import load_dataset data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t is the tab character in Python +# \t 在python中是制表符的意思 drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") ``` -在进行任何类型的数据分析时,一个好的做法是抽取一个小的随机样本,以快速了解您正在处理的数据类型。在🤗数据集中,我们可以通过链接 **Dataset.shuffle()** 和 **Dataset.select()** 共同来完成抽取: +在进行数据分析时,获取一个小的随机样本以快速了解你正在处理数据的特点是一种好的实践。在🤗数据集中,我们可以通过链接 `Dataset.shuffle()` 和 `Dataset.select()` 函数创建一个随机的样本: ```py drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) -# Peek at the first few examples +# 细看前几个例子 drug_sample[:3] ``` @@ -54,20 +54,21 @@ drug_sample[:3] 'usefulCount': [36, 13, 128]} ``` -请注意,出于可以复现的目的,我们已将在**Dataset.shuffle()**选取了固定的随机数种子。 **Dataset.select()** 需要一个可迭代的索引,所以我们已经通过了 **range(1000)** 从随机打乱的数据集中选取前 1,000 个示例。从抽取的数据中,我们已经可以看到我们数据集的一些特点: +请注意,出于可以复现的目的,我们已将在 `Dataset.shuffle()` 设定了固定的随机数种子。 `Dataset.select()` 需要一个可迭代的索引,所以我们传递了 `range(1000)` 从随机打乱的数据集中抽取前 1,000 个示例。从抽取的数据中,我们已经可以看到我们数据集中有一些特殊的地方: -* **Unnamed: 0**这列看起来很像每个患者的匿名 ID。 -* **condition** 这列包含有描述健康状况的标签。 -* 评论长短不一,混合有 Python 行分隔符 (**\r\n**) 以及 HTML 字符代码,如** &\#039;**。 +* `Unnamed: 0` 这列看起来很像每个患者的匿名 ID。 +* `condition` 列包含了大小写混合的标签。 +* 评论长短不一,混合有 Python 行分隔符 ( `\r\n` ) 以及 HTML 字符代码,如 `&\#039;` 。 -让我们看看我们如何使用 🤗 Datasets 来处理这些问题。为了验证**Unnamed: 0** 列存储的是患者 ID的猜想,我们可以使用 **Dataset.unique()** 函数来验证匿名ID 的数量是否与拆分后每部分中的行数匹配: + +让我们看看我们如何使用 🤗 Datasets 来处理这些问题。为了验证 `Unnamed: 0` 列存储的是患者 ID 的猜想,我们可以使用 `Dataset.unique()` 函数来验证匿名 ID 的数量是否与分割后每个分组中的行数匹配: ```py for split in drug_dataset.keys(): assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) ``` -这似乎证实了我们的假设,所以让我们把 **Unnamed: 0** 列重命名为患者的id。我们可以使用 **DatasetDict.rename_column()**函数一次性重命名DatasetDict中共有的列: +这似乎证实了我们的假设,所以让我们把 `Unnamed: 0` 列重命名为患者的 id。我们可以使用 `DatasetDict.rename_column()` 函数来一次性重命名两个分组: ```py drug_dataset = drug_dataset.rename_column( @@ -91,17 +92,16 @@ DatasetDict({ -✏️ **试试看!** 使用 `Dataset.unique()` 函数查找训练和测试集中满足某个条件的药物经过去重之后的数量。 +✏️ **试试看!** 使用 `Dataset.unique()` 函数查找训练和测试集中的特定药物和病症的数量。 -接下来,让我们使用 **Dataset.map()**标准化所有 **condition** 标签 .正如我们在[第三章](/course/chapter3)中所做的那样,我们可以定义一个简单的函数,可以将该函数应用于**drug_dataset** 拆分后每部分的所有行: +接下来,让我们使用 `Dataset.map()` 来规范所有的 `condition` 标签。正如我们在 [第三章](/course/chapter3) 中处理 tokenizer 一样,我们可以定义一个简单的函数,可以使用该函数 `drug_dataset` 处理每个分组的所有行: ```py def lowercase_condition(example): return {"condition": example["condition"].lower()} - drug_dataset.map(lowercase_condition) ``` @@ -109,26 +109,26 @@ drug_dataset.map(lowercase_condition) AttributeError: 'NoneType' object has no attribute 'lower' ``` -哦不,我们的map功能遇到了问题!从错误中我们可以推断出 **condition** 列存在 **None** , 不能转换为小写,因为它们不是字符串。让我们使用 **Dataset.filter()** 删除这些行 ,其工作方式类似于 **Dataset.map()** 。例如: +哦不,我们的 map 函数遇到了问题!从错误中我们可以推断出 `condition` 列存在 `None` ,不能转换为小写,因为它们不是字符串。让我们使用 `Dataset.filter()` 删除这些行 其工作方式类似于 `Dataset.map()` 。例如: ```py def filter_nones(x): return x["condition"] is not None ``` -然后运行 **drug_dataset.filter(filter_nones)** ,我们可以在一行中使用lambda 函数.在 Python 中,lambda 函数是您无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式: +然后运行 `drug_dataset.filter(filter_nones)` ,我们可以用 lambda 函数在一行代码完成这个任务。在 Pyhton 中,lambda 函数是你无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式: ``` lambda : ``` -其中**lambda** 是 Python 的特殊[关键字](https://docs.python.org/3/reference/lexical_analysis.html#keywords), **arguments** 是以逗号进行分隔的函数输入的列表/集合, **expression** 代表您希望执行的操作。例如,我们可以定义一个简单的 lambda 函数来对一个数字进行平方,如下所示: +其中 `lambda` 是 Python 的特殊 [关键字](https://docs.python.org/3/reference/lexical_analysis.html#keywords) 之一, `arguments` 是以逗号进行分隔的函数参数的列表/集合, `expression` 代表你希望执行的操作。例如,我们可以定义一个简单的 lambda 函数来对一个数字进行平方,如下所示: ``` lambda x : x * x ``` -我们需要将要输入给这个函数值括在括号中: +我们需要将要输入放在括号中: ```py (lambda x: x * x)(3) @@ -138,7 +138,7 @@ lambda x : x * x 9 ``` -类似地,我们可以通过用逗号分隔多个参数来定义 lambda 函数。例如,我们可以按如下方式计算三角形的面积: +同样,我们可以通过使用逗号分隔来定义带有多个参数的 lambda 函数。例如,我们可以按如下方式计算三角形的面积: ```py (lambda base, height: 0.5 * base * height)(4, 8) @@ -148,17 +148,17 @@ lambda x : x * x 16.0 ``` -当您想定义小型、一次性使用的函数时,Lambda 函数非常方便(有关它们的更多信息,我们建议阅读安德烈·布尔高写的[真正的Python教程](https://realpython.com/python-lambda/))。在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来消除我们数据集中的 **None** 条目: +当你想定义小型、一次性使用的函数时,lambda 函数非常方便(有关它们的更多信息,我们建议阅读 Andre Burgaud 写的 [真正的Python教程](https://realpython.com/python-lambda/) )。在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来删除我们数据集中的所有`condition` 为 `None`的记录: ```py drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) -``` +``` -当 **None** 条目已删除,我们可以标准化我们的 **condition** 列: +含有`None`的积累删除之后,我们可以规范我们的 `condition` 列: ```py drug_dataset = drug_dataset.map(lowercase_condition) -# Check that lowercasing worked +# 检查一下转换后的结果 drug_dataset["train"]["condition"][:3] ``` @@ -168,22 +168,22 @@ drug_dataset["train"]["condition"][:3] 有用!现在我们已经清理了标签,让我们来看看清洗后的评论文本。 -## 创建新的数据列 [[创建新的数据列]] +## 创建新的列 [[创建新的列]] -每当您处理客户评论时,一个好的做法是检查每个评论中的字数。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章,根据实际的情况,您需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用基于空格分割每个文本的粗略方法。 +每当我们处理客户评论时,一个好的习惯是检查评论的字数的分布。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章。在不同的使用场景,你需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用空格分割每个文本进行粗略统计。 -让我们定义一个简单的函数来计算每条评论中的单词数: +让我们定义一个简单的函数,计算每条评论的字数: ```py def compute_review_length(example): return {"review_length": len(example["review"].split())} ``` -与我们的 `lowercase_condition()` 函数不同,`compute_review_length()` 返回一个字典,其键与数据集中的列名之一不对应。 在这种情况下,当 `compute_review_length()` 传递给 `Dataset.map()` 时,它将应用于数据集中的所有行以创建新的 `review_length` 列: +不同于我们的 `lowercase_condition()` 函数, `compute_review_length()` 返回一个字典,其键并不对应数据集中的某一列名称。在这种情况下,当 `compute_review_length()` 传递给 `Dataset.map()` 时,它将处理数据集中的所有行,最后返回值会创建一个新的 `review_length` 列: ```py drug_dataset = drug_dataset.map(compute_review_length) -# Inspect the first training example +# 检查第一个训练样例 drug_dataset["train"][0] ``` @@ -198,7 +198,7 @@ drug_dataset["train"][0] 'review_length': 17} ``` -正如预期的那样,我们可以看到一个 **review_length** 列已添加到我们的训练集中。我们可以使用 **Dataset.sort()**对这个新列进行排序,然后查看极端长度的评论的样子: +正如预期的那样,我们可以看到一个 `review_length` 列已添加到我们的训练集中。我们可以使用 `Dataset.sort()` 对这个新列进行排序,然后查看一下极端长度的评论是什么样的: ```py drug_dataset["train"].sort("review_length")[:3] @@ -215,15 +215,15 @@ drug_dataset["train"].sort("review_length")[:3] 'review_length': [1, 1, 1]} ``` -正如我们所猜想的那样,一些评论只包含一个词,虽然这对于情感分析来说可能没问题,但如果我们想要预测病情,这些评论可能并不适合。 +正如我们所猜想的那样,有些评论只包含一个词,虽然这对于情感分析任务来说还可以接受,但如果我们想要预测病情,那么它所提供的信息就不够丰富了。 -🙋向数据集添加新列的另一种方法是使用函数Dataset.add_column() 。这允许您输入Python 列表或 NumPy,在不适合使用Dataset.map()情况下可以很方便。 +🙋向数据集添加新列的另一种方法是使用函数 `Dataset.add_column()` ,在使用它时你可以通过 Python 列表或 NumPy 数组的方式提供数据,在不适合使用 `Dataset.map()` 情况下可以很方便。 -让我们使用 **Dataset.filter()** 功能来删除包含少于 30 个单词的评论。与我们对 **condition** 列的处理相似,我们可以通过选取评论的长度高于此阈值来过滤掉非常短的评论: +让我们使用 `Dataset.filter()` 功能来删除包含少于 30 个单词的评论。这与我们过滤 `condition` 列的处理方式相似,我们可以通过设定评论长度的最小阈值,筛选出过短的评论: ```py drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) @@ -234,15 +234,15 @@ print(drug_dataset.num_rows) {'train': 138514, 'test': 46108} ``` -如您所见,这已经从我们的原始训练和测试集中删除了大约 15% 的评论。 +如你所见,这个操作从我们的原始训练和测试集中删除了大约 15% 的评论。 -✏️ 试试看!使用 Dataset.sort() 函数查看单词数最多的评论。请参阅文档以了解您需要使用哪个参数按长度降序对评论进行排序。 +✏️ **试试看!**使用 `Dataset.sort()` 函数查看单词数最多的评论。你可以参阅 [文档](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) 了解如何按照评论的长度降序排序。 -我们需要处理的最后一件事是评论中是否存在 HTML 字符代码。我们可以使用 Python 的**html**模块取消这些字符的转义,如下所示: +我们需要处理的最后一件事是处理评论中的 HTML 字符。我们可以使用 Python 的 `html` 模块来解码这些字符,如下所示: ```py import html @@ -255,19 +255,19 @@ html.unescape(text) "I'm a transformer called BERT" ``` -我们将使用 **Dataset.map()** 对我们语料库中的所有 HTML 字符进行转义: +我们将使用 `Dataset.map()` 对我们语料库中的所有 HTML 字符进行解码: ```python drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) ``` -如您所见, **Dataset.map()** 方法对于处理数据非常有用——在示例中仅仅是浅尝辄止就有很大的收获! +如你所见, `Dataset.map()` 方法对于处理数据非常有用,即使我们还没有完全了解它的所有功能! -## map() 方法的超级加速 [[map() 方法的超级加速]] +## `map()` 方法的超级加速 [[`map()` 方法的超级加速]] -**Dataset.map()** 方法有一个 **batched** 参数,如果设置为 **True** , map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行转义的 map 函数运行需要一些时间(您可以从进度条中读取所用时间)。我们可以通过使用列表推导同时处理多个元素来加快速度。 +`Dataset.map()` 方法有一个 `batched` 参数,如果设置为 `True` ,map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行解码的 map 函数运行需要一些时间(你可以从进度条中看到所需的时间)。我们可以通过使用列表推导同时处理多个元素来加速。 -当您在使用 **Dataset.map()**函数时指定 **batched=True**。该函数会接收一个包含数据集字段的字典,每个值都是一个列表,而不仅仅是单个值。**Dataset.map()** 的返回值应该是相同的:一个包含我们想要更新或添加到数据集中的字段的字典,字典的键是要添加的字段,字典的值是结果的列表。例如,这是使用 **batched=True**对所有 HTML 字符进行转义的方法 : +当你在使用 `Dataset.map()` 函数时设定 `batched=True` 。该函数需要接收一个包含数据集字段的字典,字典的值是一个列表。例如,这是使用 `batched=True` 对所有 HTML 字符进行解码的方法 ```python new_drug_dataset = drug_dataset.map( @@ -275,84 +275,78 @@ new_drug_dataset = drug_dataset.map( ) ``` -如果您在笔记本中运行此代码,您会看到此命令的执行速度比前一个命令快得多。这不是因为我们的评论已经是处理过的——如果你重新执行上一节的指令(没有 **batched=True** ),它将花费与以前相同的时间。这是因为列表推导式通常比在同一代码中用 **for** 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。 +如果你在笔记本中运行此代码,你会看到此命令的执行速度比前一个命令快得多。这不是因为我们的评论已经是处理过的——如果你重新执行上一节的指令(没有 `batched=True` ),它将花费与之前相同的时间。这是因为列表推导式通常比在同一代码中用 `for` 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。 -在[第六章](/course/chapter6)我们将遇到的“快速”标记器,它可以快速标记大文本列表。使用 **Dataset.map()** 和 **batched=True** 是加速的关键。例如,要使用快速标记器标记所有药物评论,我们可以使用这样的函数: +在 [第六章](/course/chapter6) 我们将遇到的“快速” tokenizer 它可以快速对长文本列表进行 tokenize。使用 `Dataset.map()` 搭配 `batched=True` 参数是加速的关键。例如,要使用快速 tokenizer 对所有药物评论 tokenize,我们可以使用如下的函数: ```python from transformers import AutoTokenizer - tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - def tokenize_function(examples): return tokenizer(examples["review"], truncation=True) ``` -正如你在[第三章](/course/chapter3)所看到的,我们原本就可以将一个或多个示例传递给分词器,因此在**batched=True**是一个非必须的选项.让我们借此机会比较不同选项的性能。在笔记本中,您可以在您要测量的代码行之前添加 **%time**来测试改行运行所消耗的时间: +正如我们在 [第三章](/course/chapter3) 所看到的,我们原本就可以将一个或多个示例传递给 tokenizer,因此在 `batched=True` 是一个非必须的选项。让我们借此机会比较不同选项的性能。在 notebook 中,你可以在你要测量的代码行之前添加 `%time` 来记录该行运行所消耗的时间: ```python no-format %time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) ``` -您还可以通过将整个单元格计时 **%%time** 在单元格的开头。在我们执行此操作的硬件上,该指令显示 10.8 秒(这是写在“Wall time”之后的数字)。 +你也可以将 `%%time` 放置在单元格开头来统计整个单元格的执行时间。在我们的硬件上,该指令显示 10.8 秒(这就是真正(Wall time)的执行时间)。 -✏️ **试试看!** 使用和不使用 `batched=True` 执行相同的指令,然后使用慢速标记器尝试(在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`),这样你就可以看看在你的电脑上它需要多长的时间。 +✏️ **试试看!** 在有和无 `batched=True` 的情况下执行相同的指令,然后试试慢速 tokenizer (在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False` ),这样你就可以测试一下在你的电脑上它需要多长的时间。 -以下是我们在使用和不使用批处理时使用快速和慢速分词器获得的结果: +以下是我们在使用和不使用批处理时使用快速和慢速 tokenizer 获得的结果: -Options | Fast tokenizer | Slow tokenizer +选项 | 快速 tokenizer | 慢速 tokenizer :--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s +`batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s -这意味着使用带有 **batched=True** 选项比没有批处理的慢选项快 30 倍——这真是太棒了!这就是为什么**AutoTokenizer** 的默认设置是**use_fast=True**的主要原因 (以及为什么它们被称为“快速”)。他们能够实现这样的加速,因为在底层的标记化代码是在 Rust 中执行的,Rust 是一种可以轻松并行化执行的语言。 +这意味着使用快速 tokenizer 配合 `batched=True` 选项比没有批处理的慢速版本快 30 倍——这真的太 Amazing 了!这就是为什么在使用 `AutoTokenizer` 时,将会默认使用 `use_fast=True` 的主要原因 (以及为什么它们被称为“快速”的原因)。他们能够实现这样的加速,因为在底层的 tokenize 代码是在 Rust 中执行的,Rust 是一种可以易于并行化执行的语言。 -并行化也是快速标记器通过批处理实现近 6 倍加速的原因:单个标记化操作是不能并行的,但是当您想同时标记大量文本时,您可以将执行拆分为多个进程,每个进程都对自己的文本负责。 - -**Dataset.map()** 也有一些自己的并行化能力。由于它们不受 Rust 的支持,因此慢速分词器的速度赶不上快速分词器,但它们仍然会更快一些(尤其是当您使用没有快速版本的分词器时)。要启用多处理,请在**Dataset.map()**时使用 **num_proc** 参数并指定要在调用中使用的进程数 : +并行化也是快速 tokenizer 通过批处理实现近 6 倍加速的原因:单个 tokenize 操作是不能并行的,但是当你想同时对大量文本进行 tokenize 时,你可以将执行过程拆分为多个进程,每个进程负责处理自己的文本。 `Dataset.map()` 也有一些自己的并行化能力。尽管它们没有 Rust 提供支持,但它们仍然可以帮助慢速 tokenizer 加速(尤其是当你使用的 tokenizer 没有快速版本时)。要启用多进程处理,请在调用 `Dataset.map()` 时使用 `num_proc` 参数并指定要在调用中使用的进程数 ```py slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) - 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 似乎产生了最好的速度增益。以下是我们在使用和不使用多处理时所需要的时间: +你可以对处理进行一些计时的试验,以确定最佳进程数;在我们的例子中,8 似乎产生了最好的速度增益。以下是我们在有无多进程处理的情况下,得到的结果: -Options | Fast tokenizer | Slow tokenizer -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s -`batched=True`, `num_proc=8` | 6.52s | 41.3s -`batched=False`, `num_proc=8` | 9.49s | 45.2s +选项 | 快速 tokenizer | 慢速 tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True` , `num_proc=8` | 6.52s | 41.3s +`batched=False` , `num_proc=8` | 9.49s | 45.2s -对于慢速分词器来说,这些结果要合理得多,但快速分词器的性能也得到了显着提高。但是请注意,情况并非总是如此——除了 **num_proc=8**,我们的测试表明,使用**batched=True**而不带有**num_proc**参数的选项处理起来更快。通常,我们不建议将 Python 多线程处理用于具有**batched=True**功能的快速标记器 . +这个结果对于慢速分词器来说是更加友好了,但快速分词器的性能也得到了显著提升。但是请注意,情况并非总是如此—对于 `num_proc` 的其他值,在我们的测试中,使用 `batched=True` 而不带有 `num_proc` 参数的选项处理起来更快。总的来说,我们并不推荐在快速 tokenizer 和 `batched=True` 的情况下使用 Python 的多进程处理。 -使用num_proc以加快处理速度通常是一个好主意,只要您使用的函数还没有自己带有的进行某种多进程处理的方法。 +通常来说,使用 `num_proc` 以加快处理速度通常是一个好主意,只要你使用的函数本身没有进行某种类型的多进程处理。 -将所有这些功能浓缩到一个方法中已经非常了不起,但还有更多!使用 **Dataset.map()** 和 **batched=True** 您可以更改数据集中的元素数量。当你想从一个例子中创建几个训练特征时,这是非常有用的。我们将在[第七章](/course/chapter7).中进行的几个NLP任务的预处理中使用到这个功能,它非常便利。 +将所有这些功能浓缩到一个方法中已经非常了不起,但是还有更多!使用 `Dataset.map()` 和 `batched=True` 你可以更改数据集中的元素数量。当你想从一个样本中创建几个训练特征时,这是非常有用的。我们将在 [第七章](/course/chapter7) 中几个 NLP 任务的预处理中使用到这个功能,它非常便捷。 -💡在机器学习中,一个例子通常可以为我们的模型提供一组特征。在某些情况下,这些特征会储存在数据集的几个列,但在其他情况下(例如此处的例子和用于问答的数据),可以从单个示例的一列中提取多个特征 +💡在机器学习中,一个样本通常可以为我们的模型提供一组特征。在某些情况下,这组特征会储存在数据集的几个列,但在某些情况下(例如此处的例子和用于问答的数据),可以从单个样本的那一列中提取多个特征。 -让我们来看看它是如何工作的!在这里,我们将标记化我们的示例并将最大截断长度设置128,但我们将要求标记器返回全部文本块,而不仅仅是第一个。这可以用 **return_overflowing_tokens=True** : +让我们来看看从一列中提取多个特征是如何实现的!在这里,我们将对我们的样本进行 tokenize 并将最大截断长度设置为 128,并且我们将要求 tokenizer 返回全部文本块,而不仅仅是第一个。这可以通过设置 `return_overflowing_tokens=True` 来实现: ```py def tokenize_and_split(examples): @@ -364,7 +358,7 @@ def tokenize_and_split(examples): ) ``` -在使用**Dataset.map()** 正式在整个数据集上开始处理之前让我们先在一个例子上测试一下: +在使用 `Dataset.map()` 正式开始处理整个数据集之前,让我们先在一个样本上测试一下: ```py result = tokenize_and_split(drug_dataset["train"][0]) @@ -375,7 +369,7 @@ result = tokenize_and_split(drug_dataset["train"][0]) [128, 49] ``` -瞧!我们在训练集中的第一个示例变成了两个特征,因为它被标记为超过我们指定的最大截断长度,因此结果被截成了两段:第一段长度为 128 ,第二段长度为 49 。现在让我们对所有元素执行此操作数据集! +瞧!我们在训练集中的第一个样本变成了两个特征,因为它超过了我们指定的最大截断长度,因此被截成了两段:第一段长度为 128 第二段长度为 49 现在让我们对数据集的所有样本执行此操作! ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -385,9 +379,9 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -不好了!它没有起作用!为什么呢?查看错误消息会给我们一个线索:列的长度不匹配,一列长度为 1,463,另一列长度为 1,000。1,000行的"review"给出了 1,463 行的新特征,导致和原本的1000行数据不匹配。 +不好了!这并没有成功!为什么呢?查看错误消息会给我们一个线索:列的长度不匹配,一列长度为 1,463,另一列长度为 1,000。1,000 行的“重新”生成了 1,463 行的新特征,导致和原本的 1000 行的长度不匹配。 -问题出在我们试图混合两个不同大小的不同数据集: **drug_dataset** 列将有一定数量的元素(我们错误中的 1,000),但是我们正在构建**tokenized_dataset** 将有更多的元素(错误消息中的 1,463)。这不适用于 **Dataset** ,因此我们需要从旧数据集中删除列或使它们的大小与新数据集中的大小相同。我们可以用 **remove_columns** 参数: +问题出在我们试图混合两个长度不同的数据集: `drug_dataset` 列将有 1000 个样本,但是我们正在构建 `tokenized_dataset` 列将有 1,463 个样本(因为我们使用 `return_overflowing_tokens=True` 将长评论分词成了多个样本)。这对 `Dataset` 来说不可行,所以我们需要要么删除旧数据集的列,要么使它们与新数据集中的尺寸相同。我们可以使用 `remove_columns` 参数来实现前者: ```py tokenized_dataset = drug_dataset.map( @@ -395,7 +389,7 @@ tokenized_dataset = drug_dataset.map( ) ``` -现在这个过程没有错误。我们可以通过比较长度来检查新数据集的元素是否比原始数据集多得多: +现在这个过程没有错误。我们可以通过比较长度来检查我们的新数据集是否比原始数据集有更多的元素: ```py len(tokenized_dataset["train"]), len(drug_dataset["train"]) @@ -405,7 +399,7 @@ len(tokenized_dataset["train"]), len(drug_dataset["train"]) (206772, 138514) ``` -我们提到我们还可以通过使旧列与新列的大小相同来处理长度不匹配的问题。为此,我们可以使用 **overflow_to_sample_mapping** 字段,当我们设置**return_overflowing_tokens=True** .它为我们提供了特征到它所产生的样本的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性: +我们也可以通过使旧列与新列保持相同大小来处理不匹配长度的问题。为此,当我们设置 `return_overflowing_tokens=True` 时,可以使用 `overflow_to_sample_mapping` 字段。它给出了新特征索引到它源自的样本索引的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性: ```py def tokenize_and_split(examples): @@ -415,14 +409,14 @@ def tokenize_and_split(examples): max_length=128, return_overflowing_tokens=True, ) - # Extract mapping between new and old indices + # 提取新旧索引之间的映射 sample_map = result.pop("overflow_to_sample_mapping") for key, values in examples.items(): result[key] = [values[i] for i in sample_map] return result ``` -我们可以使用**Dataset.map()**来进行批处理,这样无需我们删除旧列: +可以看到它可以与 `Dataset.map()` 一起协作,无需我们删除旧列: ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -442,21 +436,21 @@ DatasetDict({ }) ``` -我们获得了与以前相同数量的训练特征,但在这里我们保留了所有旧字段。如果您在使用模型计算之后需要它们进行一些后处理,您可能需要使用这种方法。 +我们获得了与之前数量相同的训练特征,并且在这里我们保留了所有旧字段。如果你在使用模型计算之后需要它们进行一些后续处理,你可能需要使用这种方法。 -您现在已经了解了 🤗 Datasets如何以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时您可能需要切换到 Pandas 以使用更强大的功能,例如 **DataFrame.groupby()** 或用于可视化的高级 API。幸运的是,🤗 Datasets旨在与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何工作的。 +你现在已经了解了如何使用 🤗 Datasets 以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时你可能需要切换到 Pandas 以使用更强大的功能,例如 `DataFrame.groupby()` 或用于可视化的高级 API。幸运的是,🤗 Datasets 设计宗旨就是与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何实现的。 -## `🤗 Datasets 和 DataFrames 的相互转换 [[`🤗 Datasets 和 DataFrames 的相互转换]] +## 🤗 Datasets 和 DataFrames 的相互转换 [[ 🤗 Datasets 和 DataFrames 的相互转换]] -为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 **Dataset.set_format()** 功能。此功能可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式,即 Apache Arrow。格式化会在数据本身上进行。为了演示,让我们将数据集转换为 Pandas: +为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 `Dataset.set_format()` 函数。此函数可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式(以 Apache Arrow 方式进行存储)。为了演示,让我们把数据集转换为 Pandas: ```py drug_dataset.set_format("pandas") ``` -现在,当我们访问数据集的元素时,我们会得到一个 **pandas.DataFrame** 而不是字典: +现在,当我们访问数据集的元素时,我们会得到一个 `pandas.DataFrame` 而不是字典: ```py drug_dataset["train"][:3] @@ -482,7 +476,7 @@ drug_dataset["train"][:3] 95260 Guanfacine adhd - "My son is halfway through his fourth week of Intuniv..." + "My son is halfway through his fourth week of Intuniv." 8.0 April 27, 2010 192 @@ -493,7 +487,7 @@ drug_dataset["train"][:3] 92703 Lybrel birth control - "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..." + "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects." 5.0 December 14, 2009 17 @@ -504,7 +498,7 @@ drug_dataset["train"][:3] 138000 Ortho Evra birth control - "This is my first time using any form of birth control..." + "This is my first time using any form of birth control." 8.0 November 3, 2015 10 @@ -513,7 +507,7 @@ drug_dataset["train"][:3] -让我们创建一个 **pandas.DataFrame** 来选择 **drug_dataset[train]** 的所有元素: +接下来我们从数据集中选择 `drug_dataset[train]` 的所有数据来得到训练集数据: ```py train_df = drug_dataset["train"][:] @@ -521,12 +515,12 @@ train_df = drug_dataset["train"][:] -🚨 在底层,`Dataset.set_format()` 改变了数据集的 `__getitem__()` dunder 方法的返回格式。 这意味着当我们想从 `"pandas"` 格式的 `Dataset` 中创建像 `train_df` 这样的新对象时,我们需要对整个数据集进行切片以获得 `pandas.DataFrame`。 无论输出格式如何,您都可以自己验证 `drug_dataset["train"]` 的类型依然还是 `Dataset`。 +🚨 实际上, `Dataset.set_format()` 仅仅改变了数据集的 `__getitem__()` 方法的返回格式。这意味着当我们想从 `"pandas"` 格式的 `Dataset` 中创建像 `train_df` 这样的新对象时,我们需要对整个数据集进行切片([:])才可以获得 `pandas.DataFrame` 对象。无论输出格式如何,你都可以自己验证 `drug_dataset["train"]` 的类型依然还是 `Dataset` 。 -从这里我们可以使用我们想要的所有 Pandas 功能。例如,我们可以通过花式链接来计算 **condition**类之间的分布 : +有了这个基础,我们可以使用我们想要的所有 Pandas 功能。例如,我们可以巧妙地链式操作,来计算 `condition` 列中不同类别的分布 ```py frequencies = ( @@ -577,7 +571,7 @@ frequencies.head() -一旦我们完成了 Pandas 分析,我们总是通过使用对象 **Dataset.from_pandas()**方法可以创建一个新的 **Dataset** 如下: +当我们完成了 Pandas 分析之后,我们可以使用对象 `Dataset.from_pandas()` 方法可以创建一个新的 `Dataset` 对象,如下所示: ```py @@ -596,11 +590,11 @@ Dataset({ -✏️ **试试看!** 计算每种药物的平均评级并将结果存储在一个新的Dataset. +✏️**试试看!**计算每种药物的平均评分并将结果存储在一个新的 Dataset 中。 -我们对 🤗 Datasets中可用的各种预处理技术的介绍到此结束。在最后一部分,让我们创建一个验证集来准备用于训练分类器的数据集。在此之前,我们将输出格式 **drug_dataset** 从 **pandas**重置到 **arrow** : +到此为止,我们对🤗 Datasets 中可用的各种预处理技术的介绍就结束了。在本节的最后一部分,让我们为训练分类器创建一个验证集。在此之前,让我们将输出格式 `drug_dataset` 从 `pandas` 重置到 `arrow` : ```python drug_dataset.reset_format() @@ -608,15 +602,15 @@ drug_dataset.reset_format() ## 创建验证集 [[创建验证集]] -尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦您对模型在测试集上的表现感到满意,您就可以对验证集进行最终的检查。此过程有助于降低您过拟合测试集并部署在现实世界数据上失败的模型的风险。 +尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦你对模型在测试集上的表现感到满意,你就可以使用验证集进行最终的检查。此过程有助于降低你过拟合测试集和部署在现实世界数据上失败的模型的风险。 -🤗 Datasets提供了一个基于**scikit-learn**的经典方法**Dataset.train_test_split()** .让我们用它把我们的训练集分成 **train** 和 **validation** (为了可以复现,我们将设置**seed**的值为一个常量): +🤗 Datasets 提供了一个基于 `scikit-learn` 的经典方法: `Dataset.train_test_split()` 。让我们用它把我们的训练集分成 `train` 和 `validation` (为了可以复现,我们将设置 `seed` 的值为一个常量): ```py drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) -# Rename the default "test" split to "validation" +# 将默认的 "test" 部分重命名为 "validation" drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") -# Add the "test" set to our `DatasetDict` +# 将 "test" 部分添加到我们的 `DatasetDict` 中 drug_dataset_clean["test"] = drug_dataset["test"] drug_dataset_clean ``` @@ -638,19 +632,19 @@ DatasetDict({ }) ``` -太好了,我们现在已经准备好了一个数据集,可以用来训练一些模型了!在[第五节]](/course/chapter5/5)我们将向您展示如何将数据集上传到 Hugging Face Hub,但现在让我们查看在本地计算机上保存数据集的几种方法。 +太好了,我们现在已经准备好了一个适合训练模型的数据集了!在 [第五节](/course/chapter5/5) 我们将向你展示如何将数据集上传到 Hugging Face Hub,现在让我们先结束我们的分析,看一看在本地计算机上保存数据集的几种方法。 ## 保存数据集 [[保存数据集]] -虽然 🤗 Datasets 会缓存每个下载的数据集和对它执行的操作,但有时你会想要将数据集保存到磁盘(例如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要功能来以不同的格式保存您的数据集: +虽然 🤗 Datasets 会缓存每个下载的数据集和对它执行的操作,但有时你会想要将数据集保存到磁盘(比如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要函数来以不同的格式保存你的数据集: | 数据格式 | 对应的方法 | | :---------: | :--------------------: | | Arrow | `Dataset.save_to_disk()` | -| CSV | `Dataset.to_csv()` | -| JSON | `Dataset.to_json()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | 例如,让我们以 Arrow 格式保存我们清洗过的数据集: @@ -679,9 +673,9 @@ drug-reviews/ └── state.json ``` -在那里我们可以看到每个部分.arrow表,以及一些元数据数据集信息.json和状态文件保存在一起.您可以将 Arrow 格式视为一个精美的列和行的表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。 +其中,我们可以看到,每个部分都有 `dataset.arrow` 表,以及保存元数据的 `dataset_info.json` 和 `state.json` 。你可以将 Arrow 格式视为一个优化过的列和行的精美表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。 -保存数据集后,我们可以使用 **load_from_disk()** 功能从磁盘读取数据如下: +保存数据集后,我们可以使用 `load_from_disk()` 功能从磁盘读取数据: ```py from datasets import load_from_disk @@ -707,14 +701,14 @@ DatasetDict({ }) ``` -对于 CSV 和 JSON 格式,我们必须将每个部分存储为单独的文件。一种方法是迭代**DatasetDict**中的键和值 : +对于 CSV 和 JSON 格式,我们必须将每个部分存储为单独的文件。一种方法是遍历 `DatasetDict` 中的键和值 ```py for split, dataset in drug_dataset_clean.items(): dataset.to_json(f"drug-reviews-{split}.jsonl") ``` -这将保存每个拆分都是[JSON的标准格式](https://jsonlines.org),其中数据集中的每一行都存储为一行 JSON。这是第一个示例: +这将把每个部分保存为 [JSON Lines格式](https://jsonlines.org) ,其中数据集中的每一行都存储为一行 JSON。下面是第一个例子的样子: ```py !head -n 1 drug-reviews-train.jsonl @@ -724,7 +718,7 @@ for split, dataset in drug_dataset_clean.items(): {"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125} ``` -然后我们可以使用[第二节](/course/chapter5/2)学过的技术加载 JSON 文件如下: +然后我们可以使用 [第二节](/course/chapter5/2) 中的技巧,按如下所示加载 JSON 文件 ```py data_files = { @@ -735,9 +729,9 @@ data_files = { drug_dataset_reloaded = load_dataset("json", data_files=data_files) ``` -这就是我们探索 🤗 Datasets 的旅程!现在我们有了一个清洗过的数据集,以下是您可以尝试的一些想法: +至此,我们对使用🤗 Datasets 进行数据整理的探索就此结束!现在我们有了一个清洗过的数据集,以下是你可以尝试的一些想法: -1. 使用[第3章](/course/chapter3)的技术来训练一个分类器,它可以根据药物评论预测病人的情况。 -2. 使用 [Chapter 1](/course/chapter1) 中的“summarization”管道生成评论摘要。 +1. 使用 [第三章](/course/chapter3) 的技术来训练一个分类器,它能够基于药品评价预测患者的病情。 +2. 使用 [第一章](/course/chapter1) 中的 `summarization` 管道生成评论的摘要。 -接下来,我们将看看 🤗 Datasets如何使您能够在不撑爆笔记本电脑内存的情况下处理庞大的数据集! \ No newline at end of file +接下来,我们将看看 🤗 Datasets 如何使你能够在不撑爆笔记本电脑内存的情况下处理庞大的数据集! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index bf34117ff..8625c67ae 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -1,4 +1,4 @@ -# 大数据? 🤗 Datasets 来救援! [[大数据? 🤗 Datasets 来救援!]] +# 大数据?🤗 Datasets 应对有方![[大数据?🤗 Datasets 应对有方!]] -如今,不难发现我们经常使用数GB的数据集, 特别是如果你打算从头开始预训练像 BERT 或者 GPT-2 这样的转换器。 在这种情况下, _加载_ 数据集就是一个挑战。例如, 用于预训练 GPT-2 的 WebText 语料库包含超过 800 万个文档和 40 GB 的文本 -- 将其加载到笔记本电脑的 RAM 中可能会让它抓狂! +如今,处理 GB 级别的数据集已不再罕见,特别是如果你打算从头开始预训练像 BERT 或者 GPT-2 这样的 Transormer 模型。在这种情况下,甚至 `加载(load)` 数据集都可能成为挑战。例如,用于预训练 GPT-2 的 WebText 语料库包含超过 800 万个文档和 40 GB 的文本 —— 将其加载到笔记本电脑的 RAM 中都可能会让人抓狂! -幸运的是, 🤗 Datasets 旨在克服这些限制。它通过将数据集作为内存映射文件来处理,并通过在语料库中流化条目来摆脱硬盘限制, 从而使你避免内存管理问题。 +幸运的是,🤗 Datasets 的设计旨在克服这些限制。它通过将数据集作为 `内存映射(memory-mapped)` 文件来处理,解放内存管理问题;并通过 `流式处理(streaming)` 来摆脱硬盘限制。 -在本节中, 我们将探索🤗 Datasets 的特性。它有一个称为 [the Pile](https://pile.eleuther.ai)的825 GB的语料库。 让我们开始吧! +在本节中,我们将使用一个庞大的 825 GB 语料库——被称为 [the Pile](https://pile.eleuther.ai) 的数据集,来探索🤗 Datasets 的这些功能。让我们开始吧! -## 什么是Pile? [[什么是Pile?]] +## 什么是 the Pile?[[什么是 the Pile?]] -The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在[14 GB chunks](https://the-eye.eu/public/AI/pile/), 并且你也可以下载几个[单独的组件](https://the-eye.eu/public/AI/pile_preliminary_components/)。 让我们先来看看 PubMed Abstracts 数据集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500万篇生物医学出版物的摘要的语料库。 数据集采用[JSON行格式](https://jsonlines.org) 并使用`zstandard`库进行压缩, 所以我们首先需要先安装`zstandard`库: +The Pile 是由 [EleutherAI](https://www.eleuther.ai) 创建的一个用于训练大规模语言模型的英语文本语料库。它包含各种各样的数据集,涵盖科学文章,GitHub 代码库以及过滤后的 Web 文本。训练语料库以 [14 GB 的文件块](https://the-eye.eu/public/AI/pile/) 提供,并且你也可以下载几个 [单独的组件](https://the-eye.eu/public/AI/pile_preliminary_components/) 。让我们先来看看 PubMed Abstracts 部分,它是 [PubMed](https://pubmed.ncbi.nlm.nih.gov/) 上的 1500 万篇生物医学出版物的摘要的语料库。数据集采用 [JSON Lines格式](https://jsonlines.org) 并使用 `zstandard` 库进行压缩,所以我们首先需要先安装 `zstandard` 库: ```py !pip install zstandard ``` -接下来, 我们可以使用[第二节](/course/chapter5/2)中所学的加载远程数据集的方法加载数据集: +接下来,我们可以使用 [第二节](/course/chapter5/2) 中所学的加载远程数据集的方法加载数据集: ```py from datasets import load_dataset -# This takes a few minutes to run, so go grab a tea or coffee while you wait :) +# 这需要几分钟才能运行,所以在你等待的时候去喝杯茶或咖啡 :) data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset @@ -42,15 +42,15 @@ Dataset({ }) ``` -我们可以看到我们的数据集中有 15,518,009 行和 2 列 -- 这是非常多的! +我们可以看到我们的数据集中有 15,518,009 行和 2 列 —— 如此庞大! -✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。 +✏️ 默认情况下,🤗 Datasets 会自动解压加载数据集所需的文件。如果你想保留硬盘空间,你可以把 `DownloadConfig(delete_extracted=True)` 传递给 `load_dataset()` 的 `download_config` 参数。更多详细信息,请参阅 [文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) 。 -让我们看看数据集的第一个元素的内容: +让我们看看数据集的第一个元素的内容: ```py pubmed_dataset[0] @@ -61,53 +61,53 @@ pubmed_dataset[0] 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} ``` -可以看到, 这看起来像是医学文章的摘要。 现在让我们看看我们使用了RAM的多少存储空间来加载数据集! +可以看到,这看起来像是医学文章的摘要。现在,让我们看看加载数据集所使用的 RAM! ## 内存映射的魔力 [[内存映射的魔力]] -在 Python 中测量内存使用情况的一个简单的方法是使用[`psutil`](https://psutil.readthedocs.io/en/latest/)库,它可以使用 `pip`安装, 如下所示: +测量 Python 内存使用的简单方式是使用 [`psutil`](https://psutil.readthedocs.io/en/latest/) 库,可以通过如下方式安装: ```python !pip install psutil ``` -它提供了一个 `Process` 类,这个类允许我们检查当前进程的内存使用情况, 如下所示: +它提供了一个 `Process` 类,让我们可以检查当前进程的内存使用情况,如下所示: ```py import psutil -# Process.memory_info is expressed in bytes, so convert to megabytes -print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +# Process.memory_info 是以字节为单位的,所以转换为兆字节 +print(f"使用的RAM:{psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") ``` ```python out RAM used: 5678.33 MB ``` -这里的`rss`属性是指 _常驻集_ 的大小, 它是进程在RAM中占用的内存比例。 这个测量结果也包括了 Python 编译器和我们加载的库所使用的内存, 所以实际上用于加载数据集的内存会更小一些。为了比较, 让我们使用 `dataset_size` 属性看看数据集在磁盘上有多大。 由于结果像之前一样用字节表示, 我们需要手动将其转换为GB: +这里的 `rss` 属性是指 `常驻集(resident set size)` 的大小,它是进程在 RAM 中占用的内存的部分。这个测量结果也包括了 Python 解释器和我们加载的库所使用的内存,所以实际上用于加载数据集的内存会更小一些。作为比较,让我们使用 `dataset_size` 属性看看数据集在磁盘上上的大小。由于结果像之前一样以字节为单位,我们需要手动将其转换为 GB: ```py -print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +print(f"数据集中文件的数量 : {pubmed_dataset.dataset_size}") size_gb = pubmed_dataset.dataset_size / (1024**3) -print(f"Dataset size (cache file) : {size_gb:.2f} GB") +print(f"数据集大小 (缓存文件) : {size_gb:.2f} GB") ``` ```python out -Number of files in dataset : 20979437051 -Dataset size (cache file) : 19.54 GB +数据集中文件的数量 : 20979437051 +数据集大小 (缓存文件) : 19.54 GB ``` -非常棒 -- 尽管它将近20GB, 但我们能够占用很少的RAM空间加载和访问数据集! +令人欣喜的是——尽管它将近 20GB 之大,我们却能用远小于此的 RAM 加载和访问数据集! -✏️ **试试看!** 从[subsets](https://the-eye.eu/public/AI/pile_preliminary_components/)中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每个子集解压后的大小。 +✏️ **试试看!** 从 Pile 选择一个比你的笔记本电脑或台式机的 RAM 更大的 [子集](https://the-eye.eu/public/AI/pile_preliminary_components/) ,用 🤗 Datasets 加载这个数据集,并且测量 RAM 的使用量。请注意,为了获得准确的测量结果,你需要新开一个进程执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027) 的表 1 中找到每个子集解压后的大小。 -如果你熟悉 Pandas, 这个结果可能会让人感到很意外。因为 Wes Kinney 的著名的[经验法则](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) 是你需要的RAM应该是数据集的大小的5倍到10倍。 那么 🤗 Datasets 是如何解决这个内存管理问题的呢? 🤗 Datasets 将每一个数据集看作一个[内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file), 它提供了RAM和文件系统存储之间的映射, 该映射允许库访问和操作数据集的元素, 而且无需将其完全加载到内存中。 +如果你熟悉 Pandas,这个结果可能会让人感到很惊奇。因为根据 Wes Kinney 的著名的 [经验法则](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) ,你通常需要 5 到 10 倍于你数据集大小的 RAM。那么 🤗 Datasets 是如何解决这个内存管理问题的呢?🤗 Datasets 将每一个数据集看作一个 [内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file) ,它提供了 RAM 和文件系统存储之间的映射,该映射允许 Datasets 库无需将其完全加载到内存中即可访问和操作数据集的元素。 -内存映射文件也一个在多个进程之间共享, 这使得像 `Dataset.map()`之类的方法可以并行化, 并且无需移动或者赋值数据集。在底层, 这些功能都是由[Apache Arrow](https://arrow.apache.org)内存格式和[`pyarrow`](https://arrow.apache.org/docs/python/index.html)库提供的支持, 使得数据加载和处理速度快如闪电。 (更多有关Apache Arrow的详细信息以及与Pandas的比较, 请查看[Dejan Simic's blog post](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) 为了更清晰地看到这个过程, 让我们通过迭代PubMed Abstracts数据集中的所有元素来运行一个速度测试小程序: +内存映射文件也一个在多个进程之间共享,这使得像 `Dataset.map()` 之类的方法可以在无需移动或者复制数据集的情况下实现并行化。在底层,这些功能都是由 [Apache Arrow](https://arrow.apache.org) 内存格式和 [`pyarrow`](https://arrow.apache.org/docs/python/index.html) 库实现的,这使得数据加载和处理速度快如闪电。(更多有关 Apache Arrow 的详细信息以及与 Pandas 的比较,请查看 [Dejan Simic的博客文章](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a) 。) 为了更清晰地看到这个过程,让我们通过遍历 PubMed 摘要数据集中的所有元素,运行一个小速度测试: ```py import timeit @@ -120,26 +120,25 @@ for idx in range(0, len(pubmed_dataset), batch_size): time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) print( - f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " - f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" + f"在 {time:.1f}s 内遍历了 {len(pubmed_dataset)}个示例(约 {size_gb:.1f} GB),即 {size_gb/time:.3f} GB/s" ) ``` ```python out -'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +'在64.2s内遍历了15518009个示例(约19.5 GB),即0.304 GB/s' ``` -这里我们使用了 Python的 `timeit` 模块来测量执行 `code_snippet`所耗的时间。 你通常能以十分之几GB/s到几GB/s的速度迭代数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制, 但是有时候你不得不使用一个很大的数据集, 它甚至都不能存储在笔记本电脑的硬盘上。例如, 如果我们尝试下载整个 Pile, 我们需要825GB的可用磁盘空间! 为了处理这种情况, 🤗 Datasets 提供了一个流式功能, 这个功能允许我们动态下载和访问元素, 并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。 +这里我们使用了 Python 的 `timeit` 模块来测量执行 `code_snippet` 所耗的时间。你通常能以十分之几 GB/s 到几 GB/s 的速度遍历一个数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制,但是有时候你不得不使用一个很大的数据集,它甚至都不能存储在笔记本电脑的硬盘上。例如,如果我们尝试下载整个 Pile,我们需要 825GB 的可用磁盘空间!为了处理这种情况,🤗 Datasets 提供了一个流式功能,这个功能允许我们动态下载和访问元素,并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。 -💡在 Jupyter 笔记中你还可以使用[`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)为单元格计时。 +💡在 Jupyter 笔记中你还可以使用 [`%%timeit` 魔术函数](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) 为整个单元格计时。 ## 流式数据集 [[流式数据集]] -要使用数据集流, 你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来, 让我们再次加载 PubMed Abstracts 数据集, 但是采用流模式: +要使用数据集流,你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来,让我们以流模式加载 PubMed 摘要数据集: ```py pubmed_dataset_streamed = load_dataset( @@ -147,7 +146,7 @@ pubmed_dataset_streamed = load_dataset( ) ``` -与我们在本章其他地方遇到的熟悉的 `Dataset` 不同, `streaming=True` 返回的对象是一个 `IterableDataset`。 顾名思义, 要访问 `IterableDataset` , 我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素: +不同于我们在这一章其它地方遇到的熟悉的 `Dataset` , `streaming=True` 返回的对象是一个 `IterableDataset` 。顾名思义,要访问 `IterableDataset` ,我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素: ```py @@ -159,7 +158,7 @@ next(iter(pubmed_dataset_streamed)) 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} ``` -如果您需要在训练期间标记流式数据集中的元素可以使用 `IterableDataset.map()`进行动态处理。该过程与我们在[第三章](/course/chapter3)中标记数据集的过程完全相同, 唯一的区别是输出是逐个返回的: +如果你需要在训练期间对流式数据集中的元素 tokenize,可以使用 `IterableDataset.map()` 进行在线处理,而不需要等待数据集全部加载完毕。该过程与我们在 [第三章](/course/chapter3) 中对数据集 tokenize 的过程完全相同,唯一的区别是输出是逐个返回的: ```py from transformers import AutoTokenizer @@ -175,11 +174,11 @@ next(iter(tokenized_dataset)) -💡 你可以传递 `batched=True` 来通过流式加速标记化, 如同我们在上一节看到的那样。它将逐批处理示例; 默认的批量大小为 1,000, 可以使用 `batch_size` 参数指定批量大小。 +💡 为了加速流式的 tokenize,你可以传递 `batched=True` ,就像我们在上一节看到的那样。它会批量处理示例;默认的批大小是 1000,可以通过 `batch_size` 参数指定批量大小。 -你还可以使用 `IterableDataset.shuffle()` 打乱流式数据集, 但与 `Dataset.shuffle()` 不同的是这只会打乱预定义 `buffer_size` 中的元素: +你还可以使用 `IterableDataset.shuffle()` 打乱流式数据集,但与 `Dataset.shuffle()` 不同的是这只会打乱预定义 `buffer_size` 中的元素: ```py shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) @@ -191,7 +190,7 @@ next(iter(shuffled_dataset)) 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} ``` -在这个示例中, 我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例, 它在缓冲区中的位置就会被语料库中的下一个示例填充 (即, 上述案例中的第 10,001个示例)。你还可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函数从流式数据集中选择元素, 它的作用类似于 `Dataset.select()`。例如, 要选择 PubMed Abstracts 数据集的前5个示例, 我们可以执行以下操作: +在这个例子中,我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例,它在缓冲区中的位置就会被语料库中的下一个示例填充 (即,上述案例中的第 10,001 个示例)。你还可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函数从流式数据集中选择元素,它的作用类似于 `Dataset.select()` 。例如,要选择 PubMed Abstracts 数据集的前 5 个示例,我们可以执行以下代码: ```py dataset_head = pubmed_dataset_streamed.take(5) @@ -211,16 +210,16 @@ list(dataset_head) 'text': 'Oxygen supply in rural africa: a personal experience ...'}] ``` -同样, 你可以使用 `IterableDataset.skip()` 函数将打乱的数据集拆分为训练集和验证集, 如下所示: +同样,你可以使用 `IterableDataset.skip()` 函数从打乱的数据集中创建训练集和验证集,如下所示: ```py -# Skip the first 1,000 examples and include the rest in the training set +# 跳过前 1,000 个示例 ,将其余部分创建为训练集 train_dataset = shuffled_dataset.skip(1000) -# Take the first 1,000 examples for the validation set +# 将前 1,000 个示例用于验证集 validation_dataset = shuffled_dataset.take(1000) ``` -让我们用一个常见的任务来进行我们对数据集流的最后探索: 将多个数据集组合在一起创建一个心得语料库。 🤗 Datasets 提供了一个 `interleave_datasets()` 函数, 它将一个 `IterableDataset` 对象列表组合为单个的 `IterableDataset`, 其中新数据集的元素是通过在列表中的对象交替获得的。当你试图组合大型数据集时, 这个函数特别有用, 让我们通过下面这个例子来试着组合 Pile的自由法律数据集,它是来自美国法院的51 GB的法律意见数据集: +让我们用一个常见的任务来进行我们对数据集流的最后探索:将多个数据集组合在一起创建一个新的语料库。🤗 Datasets 提供了一个 `interleave_datasets()` 函数,它将一个 `IterableDataset` 对象列表组合为单个的 `IterableDataset` ,其中新数据集的元素是交替抽取列表中的数据集获得的。当你试图组合大型数据集时,这个函数特别有用,让我们通过下面这个例子来试着组合 Pile 的 FreeLaw 数据集,这是一个包含美国法院法律意见的 51 GB 数据集: ```py law_dataset_streamed = load_dataset( @@ -239,7 +238,7 @@ next(iter(law_dataset_streamed)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} ``` -这个数据集足够大, 可以对大多数笔记本电脑的RAM有足够的压力, 但是我们已经能够毫不费力地加载和访问它! 现在我们使用 `interleave_datasets()` 函数加载来自 FreeLaw 和 PubMed Abstracts 的数据集: +这个数据集足够大,可以对大多数笔记本电脑的 RAM 有足够的压力,但是我们已经能够毫不费力地加载和访问它!现在我们使用 `interleave_datasets()` 函数将 FreeLaw 和 PubMed Abstracts 数据集的样本整合在一起: ```py from itertools import islice @@ -258,9 +257,9 @@ list(islice(combined_dataset, 2)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] ``` -这里我们使用了来自Python的 `itertools` 模块的 `islice()` 函数从合并的数据集中选择前两个示例, 并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的: +这里我们使用了来自 Python 的 `itertools` 模块的 `islice()` 函数从合并的数据集中选择前两个示例,并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的: -最后, 如果你想流式传输整个825GB的 Pile, 你可以按照如下方式获取所有准备好的文件: +最后,如果你想流式传输整个 825GB 的 Pile,你可以按照如下方式获取所有的预处理文件: ```py base_url = "https://the-eye.eu/public/AI/pile/" @@ -280,8 +279,8 @@ next(iter(pile_dataset["train"])) -✏️ **试试看!** 使用像[`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar)这样的大型 Common Crawl 语料库来创建一个流式多语言数据集, 该数据集代表你选择的国家/地区语言的口语比例。例如, 瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语, 因此你可以尝试根据根据口语比例对Oscar子集进行采用来创建瑞士语料库。 +✏️ **试试看!** 使用像 [`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar) 这样的大型 Common Crawl 语料库来创建一个流式多语言数据集,该数据集代表你选择的国家/地区语言的口语比例。例如,瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语,因此你可以尝试根据根据口语比例对 Oscar 子集进行抽样来创建一个瑞士语料库。 -你现在拥有加载和处理各种类型和大小的数据集的所需的所有工具 -- 但是除非你非常幸运, 否则在你的NLP之旅中会有一个难题, 你将不得不创建一个数据集来解决手头的问题。这就是下一节的主题! +你现在拥有加载和处理各种类型和大小的数据集的所需的所有工具 —— 但是除非你非常幸运,否则在你的 NLP 之旅中会有一个难题,你将不得不亲自创建一个数据集来解决手头的问题。这就是我们接下来要讨论的主题! diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index f6454f914..c3f203a11 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -7,36 +7,36 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section5.ipynb"}, ]} /> -有时,不存在合适的数据集适用于您构建 NLP 应用,因此您需要自己创建。在本节中,我们将向您展示如何创建一个[GitHub issues](https://github.com/features/issues/)的语料库,GitHub issues通常用于跟踪 GitHub 存储库中的错误或功能。该语料库可用于各种目的,包括: -* 探索关闭未解决的issue或拉取请求需要多长时间 -* 训练一个*多标签分类器*可以根据issue的描述(例如,“错误”、“增强”或“issue”)用元数据标记issue -* 创建语义搜索引擎以查找与用户查询匹配的issue +有时,不存在现有的合适的数据集适用于你构建 NLP 应用,因此你需要自己创建。在本节中,我们将向你展示如何创建一个由 [GitHub issues](https://github.com/features/issues/) 组成的的语料库,这些 issues 通常用于跟踪 GitHub 仓库中的错误或功能。该语料库可用于各种应用场景,包括: +* 探索解决 issue 或合并 pull 请求需要多长时间 +* 训练一个 `多标签分类器(multilabel classifier)` 可以根据 issue 的描述为 issue 标上元数据标签(例如,“bug”、“enhancement(增强功能)”或“question”) +* 创建语义搜索引擎以查找与用户查询匹配的 issue -在这里,我们将专注于创建语料库,在下一节中,我们将探索语义搜索。我们将使用与流行的开源项目相关的 GitHub issue:🤗 Datasets!接下来让我们看看如何获取数据并探索这些issue中包含的信息。 +在这里,我们将关注如何创建语料库,在下一节中,我们将探索语义搜索。原汤化原食,我们将使用一个流行的开源项目的 GitHub issue:🤗 Datasets!接下来让我们看看如何获取数据并探索这些 issue 中包含的信息。 ## 获取数据 [[获取数据]] -您可以浏览 🤗 Datasets 中的所有issue[Issues tab](https://github.com/huggingface/datasets/issues).如以下屏幕截图所示,在撰写本文时,有 331 个未解决的issue和 668 个已关闭的issue。 +你可以点击 [Issues 选项卡](https://github.com/huggingface/datasets/issues) 浏览 🤗 Datasets 中的所有 issus。如以下屏幕截图所示,在撰写本文时,有 331 个未解决的 issue 和 668 个已关闭的 issue。
The GitHub issues associated with 🤗 Datasets.
-如果您单击其中一个issue,您会发现它包含一个标题、一个描述和一组表征该issue的标签。下面的屏幕截图显示了一个示例. +如果你单击其中一个 issue,你会发现它包含一个标题、一个描述和一组标签,这些标签是对 issue 的描述。下面的屏幕截图显示了一个示例:
A typical GitHub issue in the 🤗 Datasets repository.
-要下载所有存储库的issue,我们将使用[GitHub REST API](https://docs.github.com/en/rest)投票[Issues endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues).此节点返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关issue状态的元数据等。 +我们可以使用 [GitHub REST API](https://docs.github.com/en/rest) 遍历 [Issues 节点(endpoint)](https://docs.github.com/en/rest/reference/issues#list-repository-issues) 下载所有仓库的 issue。节点(endpoint)将返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关 issue 状态的元数据等。 -下载issue的一种便捷方式是通过 **requests** 库,这是用 Python 中发出 HTTP 请求的标准方式。您可以通过运行以下的代码来安装库: +我们将使用 `requests` 库来下载,这是用 Python 中发出 HTTP 请求的标准方式。你可以通过运行以下的代码来安装 `requests` 库: ```python !pip install requests ``` -安装库后,您通过调用 **requests.get()** 功能来获取**Issues**节点。例如,您可以运行以下命令来获取第一页上的第一个Issues: +安装库后,你通过调用 `requests.get()` 功能来获取 `Issues` 节点(endpoint)。例如,你可以运行以下命令来获取第一页上的第一个 Issues: ```py import requests @@ -45,7 +45,7 @@ url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page= response = requests.get(url) ``` -这 **response** 对象包含很多关于请求的有用信息,包括 HTTP 状态码: +`response` 对象包含很多关于请求的有用信息,包括 HTTP 状态码: ```py response.status_code @@ -55,7 +55,7 @@ response.status_code 200 ``` -其中一个状态码 **200** 表示请求成功(您可以[在这里](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)找到可能的 HTTP 状态代码列表)。然而,我们真正感兴趣的是有效的信息,由于我们知道我们的issues是 JSON 格式,让我们按如下方式查看所有的信息: +其中, `200` 状态表示请求成功(你可以在 [这里](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) 找到所有可能的 HTTP 状态代码列表)。不过,我们真正感兴趣的是消息体中的有效信息,由于我们知道我们的 issues 是 JSON 格式,让我们按如下方式查看消息体的信息: ```py response.json() @@ -110,27 +110,28 @@ response.json() 'performed_via_github_app': None}] ``` -哇,这是很多信息!我们可以看到有用的字段,例如 **标题** , **内容** , **参与的成员**, **issue的描述信息**,以及打开issue的GitHub 用户的信息。 +哇,好大量的信息!我们可以看到有用的字段,我们可以看到诸如 `title` 、 `body` 和 `number` 等描述 issue 的有用字段,以及关于创建 issue 的 GitHub 用户的信息。 -✏️ 试试看!单击上面 JSON 中的几个 URL,以了解每个 GitHub issue中我url链接到的实际的地址。 +✏️ **试试看!**打开上面 JSON 中的一些 URL,以了解每个 GitHub issue 中 url 所链接的信息类型。 + -如 GitHub[文档](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 **per_page** 查询参数以减少您发出的请求数量,您仍然会遭到任何超过几千个issue的存储库的速率限制。因此,您应该关注 GitHub 的[创建个人身份令牌](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token),创建一个个人访问令牌这样您就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,您可以将其包含在请求标头中: +如 GitHub [文档](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 `per_page` 查询参数以减少你发出的请求次数,但你仍会在任何有几千个以上 issue 的仓库上触发速率限制。因此,你应该按照 GitHub 的 [说明](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) ,创建一个 `个人访问令牌(personal access token)` 这样你就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,你可以将其放在请求标头中: ```py -GITHUB_TOKEN = xxx # Copy your GitHub token here +GITHUB_TOKEN = xxx # 将你的GitHub令牌复制到此处 headers = {"Authorization": f"token {GITHUB_TOKEN}"} ``` -⚠️ 不要与陌生人共享存在GITHUB令牌的笔记本。我们建议您在使用完后将GITHUB令牌删除,以避免意外泄漏此信息。一个更好的做法是,将令牌存储在.env文件中,并使用 [`python-dotenv` library](https://github.com/theskumar/python-dotenv) 为您自动将其作为环境变量加载。 +⚠️ 不要与陌生人共享存在 `GITHUB令牌` 的笔记本。我们建议你在使用完后将 `GITHUB令牌` 删除,以避免意外泄漏。一个更好的做法是,将令牌存储在.env 文件中,并使用 [`python-dotenv`库](https://github.com/theskumar/python-dotenv) 自动加载环境变量。 -现在我们有了访问令牌,让我们创建一个可以从 GitHub 存储库下载所有issue的函数: +现在我们有了访问令牌,让我们创建一个可以从 GitHub 仓库下载所有 issue 的函数: ```py import time @@ -152,19 +153,19 @@ def fetch_issues( batch = [] all_issues = [] - per_page = 100 # Number of issues to return per page + per_page = 100 ## 每页返回的 issue 的数量 num_pages = math.ceil(num_issues / per_page) base_url = "https://api.github.com/repos" for page in tqdm(range(num_pages)): - # Query with state=all to get both open and closed issues + # 使用 state=all 进行查询来获取 open 和 closed 的issue query = f"issues?page={page}&per_page={per_page}&state=all" issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) batch.extend(issues.json()) if len(batch) > rate_limit and len(all_issues) < num_issues: all_issues.extend(batch) - batch = [] # Flush batch for next time period + batch = [] # 重置batch print(f"Reached GitHub rate limit. Sleeping for one hour ...") time.sleep(60 * 60 + 1) @@ -176,14 +177,14 @@ def fetch_issues( ) ``` -现在我们可以调用 **fetch_issues()** 批量下载所有issue,避免超过GitHub每小时的请求数限制;结果将存储在repository_name-issues.jsonl文件,其中每一行都是一个 JSON 对象,代表一个issue。让我们使用这个函数从 🤗 Datasets中抓取所有issue: +现在我们可以调用 `fetch_issues()` 它将按批次下载所有 issue,以避免超过 GitHub 每小时请求次数的限制;结果将存储在 `repository_name-issues.jsonl` 文件,其中每一行都是一个 JSON 对象,代表一个 issue。让我们使用这个函数从 🤗 Datasets 中抓取所有 issue: ```py -# Depending on your internet connection, this can take several minutes to run... +# 取决于你的网络连接,这可能需要几分钟的时间来运行... fetch_issues() ``` -下载issue后,我们可以使用我们 [section 2](/course/chapter5/2)新学会的方法在本地加载它们: +下载 issue 后,我们可以使用我们在 [第二节](/course/chapter5/2) 新学会的方法在本地加载它们: ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") @@ -197,20 +198,20 @@ Dataset({ }) ``` -太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个issue,而🤗 Datasets存储库中的[Issues 选项卡](https://github.com/huggingface/datasets/issues)总共却只显示了大约 1,000 个issue🤔?如 GitHub [文档](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user)中所述,那是因为我们也下载了所有的拉取请求: +太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个 issue,而🤗 Datasets 仓库中的 [Issues 选项卡](https://github.com/huggingface/datasets/issues) 总共却只显示了大约 1,000 个 issue🤔?如 GitHub [文档](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user) 中所述,那是因为我们不加下载了 issue 还下载了所有的 pull 请求: ->Git Hub的REST API v3认为每个pull请求都是一个issue,但并不是每个issue都是一个pull请求。因此,“Issues”节点可能在响应中同时返回issue和拉取请求。你可以通过pull_request 的 key来辨别pull请求。请注意,从“Issues”节点返回的pull请求的id将是一个issue id。 +> Git Hub 的 REST API v3 认为每个 pull 请求都是一个 issue,但事实上并不是每个 issue 都是一个 pull 请求。因此,“Issues”节点可能同时返回了 issue 和 pull 请求。你可以通过 `pull_request` 的 key 来辨别 pull 请求。请注意,从“Issues”节点返回的 pull 请求的 id 将是一个 issue id。 -由于issue和pull request的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。 +由于 issue 和 pull request 的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。 -## 清理数据 [[清理数据]] +## 清洗数据 [[清洗数据]] -上面来自 GitHub 文档的片段告诉我们, **pull_request** 列可用于区分issue和拉取请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在[第三节](/course/chapter5/3), 学习的方法,使用 **Dataset.shuffle()** 和 **Dataset.select()** 抽取一个随机样本,然后将 **html_url** 和 **pull_request** 列使用zip函数打包,以便我们可以比较各种 URL: +上面的 GitHub 文档告诉我们, `pull_request` 列可用于区分 issue 和 pull 请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在 [第三节](/course/chapter5/3) ,学习的方法,使用 `Dataset.shuffle()` 和 `Dataset.select()` 抽取一个随机样本,然后将 `html_url` 和 `pull_request` 列使用 zip 函数组合起来,以便我们可以比较各种 URL: ```py sample = issues_dataset.shuffle(seed=666).select(range(3)) -# Print out the URL and pull request entries +# 打印出 URL 和 pull 请求 for url, pr in zip(sample["html_url"], sample["pull_request"]): print(f">> URL: {url}") print(f">> Pull request: {pr}\n") @@ -227,7 +228,7 @@ for url, pr in zip(sample["html_url"], sample["pull_request"]): >> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} ``` -这里我们可以看到,每个pull请求都与各种url相关联,而普通issue只有一个None条目。我们可以使用这一点不同来创建一个新的is_pull_request列通过检查pull_request字段是否为None来区分它们: +这里我们可以看到,每个 pull 请求都与各种 url 相关联,而普通 issue 只有一个 `None` 条目。我们可以使用这一点不同来创建一个新的 `is_pull_request` 列,通过检查 `pull_request` 字段是否为 `None` 来区分它们: ```py issues_dataset = issues_dataset.map( @@ -237,21 +238,21 @@ issues_dataset = issues_dataset.map( -✏️ 试试看!计算在 🤗 Datasets中解决issue所需的平均时间。您可能会发现 Dataset.filter()函数对于过滤拉取请求和未解决的issue很有用,并且您可以使用Dataset.set_format()函数将数据集转换为DataFrame,以便您可以轻松地按照需求修改创建和关闭的时间的格式(以时间戳格式)。 +✏️ **试试看!**计算在 🤗 Datasets 中解决 issue 所需的平均时间。你可能会发现 `Dataset.filter()` 函数对于过滤 pull 请求和未解决的 issue 很有用,并且你可以使用 `Dataset.set_format()` 函数将数据集转换为 `DataFrame` ,以便你可以轻松地按照需求修改 `创建(created_at)` 和 `关闭(closed_at)` 的时间的格式(以时间戳格式)。 -尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个应用程序中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:与每个issue和拉取请求相关的评论。我们接下来将添加它们——你猜对了——我们将依然使用GitHub REST API! +尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个不同的项目中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:每个 issue 和 pull 中的评论。我们接下来将添加它们——你猜对了——我们将依然使用 GitHub REST API! ## 扩充数据集 [[扩充数据集]] -如以下屏幕截图所示,与issue或拉取请求相关的评论提供了丰富的信息,特别是如果我们有兴趣构建搜索引擎来回答用户对这个项目的疑问。 +如以下截图所示,issue 或 pull 请求相关的评论提供了丰富的信息,特别是如果我们有兴趣构建搜索引擎来回答用户对这个仓库的疑问时,这些信息将非常有用。
Comments associated with an issue about 🤗 Datasets.
-GitHub REST API 提供了一个 [评论节点](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回与issue编号相关的所有评论。让我们测试节点以查看它返回的内容: +GitHub REST API 提供了一个 [`Comments(评论)`节点](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回与 issue 编号相关的所有评论。让我们测试一下该节点返回的内容: ```py issue_number = 2792 @@ -287,11 +288,11 @@ response.json() 'created_at': '2021-08-12T12:21:52Z', 'updated_at': '2021-08-12T12:31:17Z', 'author_association': 'CONTRIBUTOR', - 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", 'performed_via_github_app': None}] ``` -我们可以看到注释存储在body字段中,所以让我们编写一个简单的函数,通过在response.json()中为每个元素挑选body内容来返回与某个issue相关的所有评论: +我们可以看到评论存储在 `body` 字段中,因此让我们编写一个简单的函数,通过挑选出 `response.json()` 中每个元素的 `body` 内容,返回与某个 issue 相关的所有评论: ```py def get_comments(issue_number): @@ -300,18 +301,18 @@ def get_comments(issue_number): return [r["body"] for r in response.json()] -# Test our function works as expected +# 测试我们的函数是否按预期工作 get_comments(2792) ``` ```python out -["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] ``` -这看起来不错,所以让我们使用 **Dataset.map()** 方法在我们数据集中每个issue的添加一个**comments**列: +这看起来不错,所以让我们使用 `Dataset.map()` 方法,为我们数据集中每个 issue 的添加一个 `comments` 列: ```py -# Depending on your internet connection, this can take a few minutes... +# 取决于你的网络连接,这可能需要几分钟时间... issues_with_comments_dataset = issues_dataset.map( lambda x: {"comments": get_comments(x["number"])} ) @@ -323,14 +324,14 @@ issues_with_comments_dataset = issues_dataset.map( -现在我们有了增强的数据集,是时候将它推送到 Hub 以便我们可以与社区共享它了! 上传数据集非常简单:就像 🤗 Transformers 中的模型和分词器一样,我们可以使用 `push_to_hub()` 方法来推送数据集。 为此,我们需要一个身份验证令牌,它可以通过首先使用 `notebook_login()` 函数登录到 Hugging Face Hub 来获得: +现在我们有了扩充后的数据集,是时候将它推送到 Hub 并与社区共享了!上传数据集非常简单:就像 🤗 Transformers 中的模型和 tokenizer 一样,我们可以使用 `push_to_hub()` 方法来推送数据集。为此,我们需要一个身份验证令牌,它可以通过首先使用 `notebook_login()` 函数登录到 Hugging Face Hub 来获得: ```py from huggingface_hub import notebook_login notebook_login() ``` -这将创建一个小部件,您可以在其中输入您的用户名和密码, API 令牌将保存在~/.huggingface/令牌.如果您在终端中运行代码,则可以改为通过 CLI 登录: +这将创建一个小部件,你可以在其中输入你的用户名和密码,API 令牌将保存在 `~/.huggingface/token` 中。如果你在终端中运行代码,则可以改为使用命令行登录: ```bash huggingface-cli login @@ -342,7 +343,7 @@ huggingface-cli login issues_with_comments_dataset.push_to_hub("github-issues") ``` -之后,任何人都可以通过便捷地提供带有存储库 ID 作为 path 参数的 load_dataset() 来下载数据集: +之后,任何人都可以通过便捷地使用附带仓库 ID 作为 `path` 参数的 `load_dataset()` 函数 来下载数据集: ```py remote_dataset = load_dataset("lewtun/github-issues", split="train") @@ -356,27 +357,29 @@ Dataset({ }) ``` -很酷,我们已经将我们的数据集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一个数据卡这解释了语料库是如何创建的,并为使用数据集的其他提供一些其他有用的信息。 +很酷,我们已经将我们的数据集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一个数据卡片,解释语料库是如何创建的,并为使用数据集的其他提供一些其他有用的信息。 -💡 您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 +💡 你还可以使用一些 Git 技巧和 `huggingface-cli` 直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets 指南](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 ## 创建数据集卡片 [[创建数据集卡片]] -有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了上下文,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。在 Hugging Face Hub 上,此信息存储在每个数据集存储库的自述文件文件。在创建此文件之前,您应该执行两个主要步骤: +有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了数据集相关的信息,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。 + +在 Hugging Face Hub 上,此信息存储在每个数据集仓库的自述文件(`README.md`)。在创建此文件之前,你应该采取两个主要步骤: -1. 使用[数据集标签应用程序](https://huggingface.co/datasets/tagging/) 创建YAML格式的元数据标签。这些标签用于各种各样的搜索功能,并确保您的数据集可以很容易地被社区成员找到。因为我们已经在这里创建了一个自定义数据集,所以您需要克隆数据集标签存储库并在本地运行应用程序。它的界面是这样的: +1. 使用 [`datasets-tagging`应用程序](https://huggingface.co/datasets/tagging/) 创建 YAML 格式的元数据标签。这些标签用于 Hugging Face Hub 上的各种搜索功能,并确保你的数据集可以很容易地被社区成员找到。由于我们已经在这里创建了一个自定义数据集,所以你需要克隆数据集标签仓库( `datasets-tagging` )并在本地运行应用程序。它的界面是这样的:
-The `datasets-tagging` interface. + datasets-tagger interface.
-2.阅读[🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 关于创建信息性数据集卡片的指南,并将其作为模板使用。 +2.阅读 [🤗 Datasets 指南](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 中关于创建完善的数据集卡片的指南,并将其作为模板使用。 -您可以创建自述文件文件直接在Hub上,你可以在里面找到模板数据集卡片 **lewtun/github-issues** 数据集存储库。填写好的数据集卡片的屏幕截图如下所示。! +你可以直接在 Hub 上创建 README.md 文件,你可以在 `lewtun/github-issues` 数据集仓库中找到一个模板数据集卡片。下面显示了填写好的数据集卡片的截图。
A dataset card. @@ -384,15 +387,14 @@ Dataset({ -✏️试试看!使用应用程序和 [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南来完成 GitHub issue数据集的 README.md 文件。 +✏️**试试看!**使用 `dataset-tagging` 应用程序和 [🤗 Datasets 指南](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南来完成 GitHub issue 数据集的 README.md 文件。 -很好! 我们在本节中看到,创建一个好的数据集可能非常复杂,但幸运的是,将其上传并与社区共享会很容易实现。在下一节中,我们将使用我们的新数据集创建一个带有 🤗 Datasets的语义搜索引擎,该数据集可以将issue与最相关的issue和评论进行匹配。 +很好!我们在本节中可以看到,创建一个好的数据集可能涉及相当多的工作,但幸运的是,将其上传并与社区共享会很容易实现。在下一节中,我们将使用我们的新数据集创建一个 🤗 Datasets 的语义搜索引擎,该引擎可以将输入匹配到最相关的 issue 和评论。 -✏️ 试试看!按照我们在本节中采取的步骤为您最喜欢的开源库创建一个 GitHub issue数据集(当然,选择 🤗 Datasets以外的其他东西!)。对于奖励积分,微调多标签分类器以预测该领域中存在的标签。 +✏️ **试试看!**按照我们在本节中采取的步骤为你最喜欢的开源库创建一个 GitHub issue 数据集(当然是除了 🤗 Datasets)。进阶的挑战:微调多标签分类器以预测在 `labels` 字段中出现的标签。 - diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index 6ded366a9..71a46b74a 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -22,13 +22,15 @@ {/if} -在[第五小节](/course/chapter5/5), 我们从 🤗 Datasets 存储库创建了一个包含 GitHub 问题和评论的数据集。在本节中,我们将使用这些信息来构建一个搜索引擎,它可以帮助我们找到这个库最紧迫问题的答案! +在 [第5小节](/course/chapter5/5) ,我们创建了一个 🤗 Datasets 仓库的 GitHub issues 和评论组成的数据集。在本节,我们将使用这些信息构建一个搜索引擎,帮助我们找到关于该库的最相关的 issue 的答案! -## 使用嵌入进行语义搜索 [[使用嵌入进行语义搜索]] +## 使用文本嵌入进行语义搜索 [[使用文本嵌入进行语义搜索]] -正如我们在[第一章](/course/chapter1),学习的, 基于 Transformer 的语言模型会将文本中的每个标记转换为嵌入向量.事实证明,可以“汇集”各个嵌入向量来创建整个句子、段落或文档(在某些情况下)的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。在本节中,我们将使用嵌入来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。 +正如我们在 [第一章](/course/chapter1) ,学习的,基于 Transformer 的语言模型会将文本中的每个 token 转换为嵌入向量。事实证明,我们可以 “池化(pool)” 嵌入向量以创建整个句子、段落或(在某些情况下)文档的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。 + +在本节中,我们将使用文本嵌入向量来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。
Semantic search. @@ -37,7 +39,7 @@ ## 加载和准备数据集 [[加载和准备数据集]] -我们需要做的第一件事是下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 `load_dataset()`函数: +首先,我们需要下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 `load_dataset()` 函数: ```py from datasets import load_dataset @@ -54,7 +56,7 @@ Dataset({ }) ``` -这里我们在load_dataset()中使用了默认的训练集分割,所以它返回一个数据集而不是数据集字典。第一项任务是过滤掉pull请求,因为这些请求很少用于回答用户提出的问题,而且会给我们的搜索引擎带来噪声。现在应该很熟悉了,我们可以使用dataset.filter()函数来排除数据集中的这些行。同时,让我们也过滤掉没有注释的行,因为这些行不会是用户提问的答案: +在此,我们在 `load_dataset()` 中指定了默认的 `train(训练集)` 部分,因此它返回一个 `Dataset` 而不是 `DatasetDict` 。首要任务是排除掉拉取请求(pull),因为这些请求往往很少用于回答提出的 issue,会为我们的搜索引擎引入干扰。正如我们现在熟悉的那样,我们可以使用 `Dataset.filter()` 函数来排除数据集中的这些行。与此同时,让我们也筛选掉没有评论的行,因为这些行没有为用户提问提供回答: ```py issues_dataset = issues_dataset.filter( @@ -70,7 +72,7 @@ Dataset({ }) ``` -我们可以看到我们的数据集中有很多列,其中大部分我们不需要构建我们的搜索引擎。从搜索的角度来看,信息量最大的列是 **title** , **body** , 和 **comments** ,而 **html_url** 为我们提供了一个回到源问题的链接。让我们使用 **Dataset.remove_columns()** 删除其余部分的功能: +我们可以看到,我们的数据集中有很多列,其中大部分在构建我们的搜索引擎都不会使用。从搜索的角度来看,信息量最大的列是 `title` , `body` ,和 `comments` ,而 `html_url` 为我们提供了一个回到原 issue 的链接。让我们使用 `Dataset.remove_columns()` 删除其余的列: ```py columns = issues_dataset.column_names @@ -87,27 +89,27 @@ Dataset({ }) ``` -为了创建我们的嵌入,我们将用问题的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 **comments** 列当前是每个问题的评论列表,我们需要“重新组合”列,以便每一条评论都包含一个 **(html_url, title, body, comment)** 元组。在 Pandas 中,我们可以使用 [DataFrame.explode() 函数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。为了看到它的实际效果,让我们首先切换到 Pandas的**DataFrame** 格式: +为了创建我们的文本嵌入数据集,我们将用 issue 的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 `comments` 列当前是每个 issue 的评论列表,我们需要“重新组合”列,使得每一行都是由一个 `(html_url, title, body, comment)` 元组组成。在 Pandas 中,我们可以使用 [DataFrame.explode() 函数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html) 完成这个操作 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。让我们首先切换到 Pandas 的 `DataFrame` 格式: ```py issues_dataset.set_format("pandas") df = issues_dataset[:] ``` -如果我们检查这里的第一行 **DataFrame** 我们可以看到有四个评论与这个问题相关: +如果我们检查这个 `DataFrame` 的第一行,我们可以看到这个 issue 有四个相关评论: ```py df["comments"][0].tolist() ``` ```python out -['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', - 'cannot connect,even by Web browser,please check that there is some problems。', + 'cannot connect,even by Web browser,please check that there is some problems。', 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] ``` -我们希望这些评论中的每一条都得到一行。让我们检查是否是这种情况: +我们希望使用 `explode()` 将这些评论中的每一条都展开成为一行。让我们看看是否可以做到: ```py comments_df = df.explode("comments", ignore_index=True) @@ -156,7 +158,7 @@ comments_df.head(4) -太好了,我们可以看到评论成功被扩充, **comments** 是包含个人评论的列!现在我们已经完成了 Pandas要完成的部分功能,我们可以快速切换回 **Dataset** 通过加载 **DataFrame** 在内存中: +非常好,我们可以看到其他三列已经被复制了,并且 `comments` 列里存放着单独的评论!现在我们已经完成了 Pandas 要完成的部分功能,我们可以通过加载内存中的 `DataFrame` 快速切换回 `Dataset` : ```py from datasets import Dataset @@ -177,11 +179,11 @@ Dataset({ -✏️ **试试看!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 +✏️ **试试看!** 看看你是否可以使用 `Dataset.map()` 展开 `issues_dataset` 的 `comments` 列,这有点棘手;你可能会发现 🤗 Datasets 文档的 ["批处理映射(Batch mapping)"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 -现在我们每行有一个评论,让我们创建一个新的 **comments_length** 列来存放每条评论的字数: +既然我们每行有一个评论,让我们创建一个新的 `comments_length` 列来存放每条评论的字数: ```py comments_dataset = comments_dataset.map( @@ -189,7 +191,7 @@ comments_dataset = comments_dataset.map( ) ``` -我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。虽然无法为过滤器选择的精确数字,但大约大于15 个单词似乎是一个不错的选择: +我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。筛选的精确数字没有硬性规定,但大约大于 15 个单词似乎是一个不错的选择: ```py comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) @@ -203,7 +205,7 @@ Dataset({ }) ``` -稍微清理了我们的数据集后,让我们将问题标题、描述和评论连接到一个新的 **text** 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 **Dataset.map()**来做到这些 : +稍微清理了我们的数据集后,让我们使用 issue 标题、描述和评论构建一个新的 `text` 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 `Dataset.map()` 来完成这些操作 ```py def concatenate_text(examples): @@ -219,11 +221,11 @@ def concatenate_text(examples): comments_dataset = comments_dataset.map(concatenate_text) ``` -我们终于准备好创建一些嵌入了!让我们来看看。 +我们终于准备好创建一些文本嵌入了!让我们继续。 ## 创建文本嵌入 [[创建文本嵌入]] -我们在[第二章](/course/chapter2) 学过,我们可以通过使用 **AutoModel** 类来完成词嵌入。我们需要做的就是选择一个合适的检查点来加载模型。幸运的是,有一个名为 **sentence-transformers** 专门用于创建词嵌入。如库中[文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的,我们这次要实现的是非对称语义搜索,因为我们有一个简短的查询,我们希望在比如问题评论等更长的文档中找到其答案。通过查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 **multi-qa-mpnet-base-dot-v1** 检查点在语义搜索方面具有最佳性能,因此我们将在我们的应用程序中使用它。我们还将使用相同的检查点加载标记器: +正如我们在 [第二章](/course/chapter2) 学过,我们可以通过使用 `AutoModel` 类来完成文本嵌入。首先需要做的就是选择一个合适的 checkpoint。幸运的是,有一个名为 `sentence-transformers` 的库专门用于创建文本嵌入。如库中的 [文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search) 所述的,我们这次要实现的是非对称语义搜索(asymmetric semantic search),因为我们有一个简短的查询,我们希望在比如 issue 评论等更长的文档中找到其匹配的文本。通过查看 [模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 `multi-qa-mpnet-base-dot-v1` checkpoint 在语义搜索方面具有最佳性能,因此我们将使用它。我们还将使用这个 checkpoint 加载了对应的 tokenizer : {#if fw === 'pt'} @@ -235,7 +237,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = AutoModel.from_pretrained(model_ckpt) ``` -为了加快嵌入过程,将模型和输入放在 GPU 设备上,所以现在让我们这样做: +将模型和输入的文本放到 GPU 上会加速嵌入过程,所以让我们现在就这么做: ```py import torch @@ -254,19 +256,18 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) ``` -请注意,我们已将 from_pt=True 设置为 from_pretrained() 方法的参数。这是因为 multi-qa-mpnet-base-dot-v1 检查点只有PyTorch权重,因此设置 from_pt=True 会自动将它们转换为TensorFlow格式。如您所见,在Transformers中的🤗框架之间切换非常简单! +请注意,我们在调用 `from_pretrained()` 方法的时候添加了 `from_pt=True` 参数。这是因为 `multi-qa-mpnet-base-dot-v1` checkpoint 只有 PyTorch 权重,因此设置 `from_pt=True` 会自动将它们转换为 TensorFlow 格式。如你所见,在🤗 Transformers 中切换框架非常简单! {/if} -正如我们之前提到的,我们希望将 GitHub 问题语料库中的每个条目表示为单个向量,因此我们需要以某种方式“池化”或平均化我们的标记嵌入。一种流行的方法是在我们模型的输出上执行CLS 池化,我们只获取**[CLS]** 令牌的最后一个隐藏状态。以下函数为我们提供了这样的方法: +根据我们之前的想法,我们希望将我们的 GitHub issue 中的每一条记录转化为一个单一的向量,所以我们需要以某种方式“池化(pool)”或平均每个词的嵌入向量。一种流行的方法是在我们模型的输出上进行 `CLS 池化` ,我们只需要收集 `[CLS]` token 的的最后一个隐藏状态。以下函数实现了这个功能: ```py def cls_pooling(model_output): return model_output.last_hidden_state[:, 0] ``` -接下来,我们将创建一个辅助函数,该函数将标记文档列表,将tensor放在 GPU 上,然后提供给模型,最后对输出使用CLS 池化: - +接下来,我们将创建一个辅助函数,它将对一组文档进行 tokenize,然后将张量放在 GPU 上,接着将它们喂给模型,最后对输出进行 CLS 池化: {#if fw === 'pt'} ```py @@ -279,7 +280,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: +我们可以将第一条数据喂给它并检查输出的形状来测试这个函数是否正常工作: ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -290,7 +291,7 @@ embedding.shape torch.Size([1, 768]) ``` -太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: +太好了,我们已经将语料库中的第一个条目转换为了一个 768 维向量!我们可以用 `Dataset.map()` 将我们的 `get_embeddings()` 函数应用到我们语料库中的每一行,然后创建一个新的 `embeddings` 列: ```py embeddings_dataset = comments_dataset.map( @@ -310,7 +311,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: +我们可以将第一个文本条目喂给它并检查输出的形状来测试这个函数是否正常工作: ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -321,7 +322,7 @@ embedding.shape TensorShape([1, 768]) ``` -太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: +太好了,我们已经将语料库中的第一个条目转换为了一个 768 维向量!我们可以用 `Dataset.map()` 将我们的 `get_embeddings()` 函数应用到我们语料库中的每一行,然后创建一个新的 `embeddings` 列: ```py embeddings_dataset = comments_dataset.map( @@ -331,18 +332,20 @@ embeddings_dataset = comments_dataset.map( {/if} -请注意,我们已经将嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 索引它们时,🤗 Datasets需要这种格式,我们接下来会这样做。 +请注意,在上面的代码中我们已经将文本嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 搜索它们时,🤗 Datasets 需要这种格式,让我们继续吧。 ## 使用 FAISS 进行高效的相似性搜索 [[使用 FAISS 进行高效的相似性搜索]] -现在我们有了一个词嵌入数据集,我们需要一些方法来搜索它们。为此,我们将在 🤗 Datasets中使用一种特殊的数据结构,称为 FAISS指数.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的缩写)是一个提供高效算法来快速搜索和聚类嵌入向量的库。FAISS 背后的基本思想是创建一个特殊的数据结构,称为指数。这允许人们找到哪些嵌入词与输入的词嵌入相似。在 🤗 Datasets中创建一个 FAISS 索引很简单——我们使用 **Dataset.add_faiss_index()** 函数并指定我们要索引的数据集的哪一列: +现在我们有了一个文本嵌入数据集,我们需要一些方法来搜索它们。为此,我们将使用🤗 Datasets 中一种特殊的数据结构,称为 FAISS 指数。 [FAISS](https://faiss.ai/) (Facebook AI Similarity Search 的缩写)是一个库,提供了用于快速搜索和聚类嵌入向量的高效算法。 + +FAISS 背后的基本思想是创建一个特殊的数据结构,称为 `index(索引)` 它可以找到哪些嵌入与输入嵌入相似。在 🤗 Datasets 中创建一个 FAISS index(索引)很简单——我们使用 `Dataset.add_faiss_index()` 函数并指定我们要索引的数据集的哪一列: ```py embeddings_dataset.add_faiss_index(column="embeddings") ``` -现在,我们可以使用**Dataset.get_nearest_examples()**函数进行最近邻居查找。让我们通过首先嵌入一个问题来测试这一点,如下所示: +现在,我们可以使用 `Dataset.get_nearest_examples()` 函数进行最近邻居查找。让我们通过首先嵌入一个 issue 来测试这一点,如下所示: {#if fw === 'pt'} @@ -370,7 +373,7 @@ question_embedding.shape {/if} -就像文档一样,我们现在有一个 768 维向量表示查询,我们可以将其与整个语料库进行比较以找到最相似的嵌入: +就像对文档进行嵌入一样,我们现在有一个表示查询的 768 维向量,我们可以将其与整个语料库进行比较以找到最相似的嵌入: ```py scores, samples = embeddings_dataset.get_nearest_examples( @@ -378,7 +381,7 @@ scores, samples = embeddings_dataset.get_nearest_examples( ) ``` - **Dataset.get_nearest_examples()** 函数返回一个分数元组,对查询和文档之间的相似度进行排序,以及一组最佳匹配的结果(这里是 5 个)。让我们把这些收集到一个 **pandas.DataFrame** 以便我们可以轻松地对它们进行排序: +`Dataset.get_nearest_examples()` 函数返回一个元组,包括评分(评价查询和文档之间的相似程度)和对应的样本(这里是 5 个最佳匹配)。让我们把这些收集到一个 `pandas.DataFrame` ,这样我们就可以轻松地对它们进行排序: ```py import pandas as pd @@ -388,7 +391,7 @@ samples_df["scores"] = scores samples_df.sort_values("scores", ascending=False, inplace=True) ``` -现在我们可以遍历前几行来查看我们的查询与评论的匹配程度: +现在我们可以遍历前几行来查看我们的查询与评论的匹配程度如何: ```py for _, row in samples_df.iterrows(): @@ -456,7 +459,7 @@ COMMENT: > here is my way to load a dataset offline, but it **requires** an onli > > import datasets > -> data = datasets.load_dataset(...) +> data = datasets.load_dataset(.) > > data.save_to_disk(/YOUR/DATASET/DIR) > @@ -506,10 +509,10 @@ URL: https://github.com/huggingface/datasets/issues/824 """ ``` -我们的第二个搜索结果似乎与查询相符。 +不错!我们的输出的第 2 个结果似乎与查询匹配。 -✏️ 试试看!创建您自己的查询并查看您是否可以在检索到的文档中找到答案。您可能需要增加参数k以扩大搜索范围。 +✏️ 试试看!创建你自己的查询并查看你是否可以在检索到的文档中找到答案。你可能需要在 `Dataset.get_nearest_examples()` 增加参数 `k` 以扩大搜索范围。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx index 9f42af397..d42422160 100644 --- a/chapters/zh-CN/chapter5/7.mdx +++ b/chapters/zh-CN/chapter5/7.mdx @@ -1,16 +1,16 @@ -# 🤗 Datasets,回顾! [[🤗 Datasets,回顾!]] +# 🤗 Datasets,完结![[🤗 Datasets,完结!]] -这是对 🤗 Datasets 库的一次完整游览——祝贺你走到这一步!凭借从本章中获得的知识,您应该能够: +这是对 🤗 Datasets 库的一次完整的探索——祝贺你走到这一步!凭借从本章中获得的知识,你应该能够: -- 从任何地方加载数据集,无论是 Hugging Face Hub、您的笔记本电脑还是您公司的远程服务器。 -- 混合使用Dataset.map()和Dataset.filter()函数来整理数据。 -- 使用`Dataset.set_format()`在 Pandas 和 NumPy 等数据格式之间快速切换. -- 创建您自己的数据集并将其推送到 Hugging Face Hub。. -- 使用 Transformer 模型为您的文档创建词嵌入,并使用 FAISS 构建语义搜索引擎。. +- 从任何地方加载数据集,无论是 Hugging Face Hub、你的笔记本电脑还是你公司的远程服务器。 +- 混合使用 `Dataset.map()` 和 `Dataset.filter()` 函数来整理数据。 +- 使用 `Dataset.set_format()` 在 Pandas 和 NumPy 等数据格式之间快速切换。 +- 创建你自己的数据集并将其推送到 Hugging Face Hub。 +- 使用 Transformer 模型为你的文档创建文本嵌入,并使用 FAISS 构建语义搜索引擎。 -在[第七章](/course/chapter7),当我们深入研究 Transformer 模型非常适合的核心 NLP 任务时,我们将充分利用所有这些。 \ No newline at end of file +在 [第七章](/course/chapter7) ,我们将把所有这些用于深入研究 Transformer 模型擅长的核心 NLP 任务。不过,在跳到下一步之前,先用一次快速的小测验来检验你对 🤗 Datasets 的了解! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx index 503108c9c..c3cf162ad 100644 --- a/chapters/zh-CN/chapter5/8.mdx +++ b/chapters/zh-CN/chapter5/8.mdx @@ -7,118 +7,118 @@ classNames="absolute z-10 right-0 top-0" /> -本章涵盖了很多方面! 如果你没有掌握所有细节, 不用担心; 在下一章将帮助你了解内部的事情是如何工作的。 +本章涵盖了很多方面!如果你没有掌握所有细节,不用担心;接下来的章节将继续帮助你了解🤗 Datasets 内在运作机制。 -不过, 在继续下一章之前, 让我们测试一下你在本章学到的内容。 +不过,在继续下一章之前,让我们测试一下你在本章学到的内容。 -### 1.🤗 Datasets中的 `load_dataset ()` 函数允许你从下列哪个位置加载数据集? +### 1.🤗 Datasets 中的 `load_dataset ()` 函数允许你从下列哪个位置加载数据集? load_dataset() 函数的 data_files 参数来加载本地数据集。", + explain: "正确!你可以将本地文件的路径传递给 `load_dataset()` 函数的 `data_files` 参数来加载本地数据集。", correct: true }, { text: "Hugging Face Hub", - explain: "正确! 你可以通过提供数据集 ID 在 Hub 上加载数据集, 例如 < code > load _ dataset ('em otion') 。", + explain: "正确!你可以通过提供数据集 ID 来加载 Hub 上的数据集,例如 `load_dataset('emotion')` 。", correct: true }, { text: "远程服务器", - explain: "正确! 你可以将URL传递给 load_dataset() 函数的 data_files 参数来加载远程文件。", + explain: "正确!你可以将 URL 传递给 `load_dataset()` 函数的 `data_files` 参数来加载远程文件。", correct: true }, ]} /> -### 2.假设您加载了 GLUE 任务,如下所示: +### 2.假设你按照以下方式加载了一个 GLUE 任务: ```py from datasets import load_dataset dataset = load_dataset("glue", "mrpc", split="train") ``` -以下哪个命令将从 `dataset` 中生成50个元素的随机样本? +以下哪个命令可以从 `dataset` 中生成 50 个元素的随机样本? dataset.sample(50)", - explain: "这是不正确的——没有 Dataset.sample() 方法。" + text: " `dataset.sample (50)` ", + explain: "不正确 —— 没有 `Dataset.sample()` 方法。" }, { - text: "dataset.shuffle().select(range(50))", - explain: "正确! 正如你在本章中看待的, 你首先打乱了数据集, 然后从中选择样本。", + text: " `dataset.shuffle().select(range(50))` ", + explain: "正确!正如你在这一章中所学到的,上面的代码首先打乱了数据集,然后从中选择样本。", correct: true }, { - text: "dataset.select(range(50)).shuffle()", - explain: "这是不正确的——尽管代码会运行, 但它只会随机处理数据集中的前50个元素。" + text: " `dataset.select(range(50)).shuffle()` ", + explain: "这是不正确的——尽管代码会运行,但它只会选取数据集的前 50 个元素然后打乱它们。" } ]} /> -### 3.假设你有一个叫做宠物数据集的家庭宠物数据集,它有一个名字列表示每个宠物的名字。下列哪种方法可以让你过滤所有名字以字母"L"开头的宠物的数据? +### 3.假设你有一个关于家庭宠物的数据集 `pets_dataset` ,它有一个 `name` 列表示每个宠物的名字。以下哪种方法可以筛选出所有名字以 "L" 开头的宠物数据? pets_dataset.filter(lambda x: x['name'].startswith('L'))", - explain: "正确! 为这些快速过滤使用 Python lambda 函数是一个好主意。你还能想到其他解决方案吗?", + text: " `pets_dataset.filter(lambda x: x ['name'].startswith ('L'))` ", + explain: "正确!使用 Python 的 lambda 函数来快速过滤是一个好主意。你能想出另一种解决方案吗?", correct: true }, { - text: "pets_dataset.filter(lambda x['name'].startswith('L')", - explain: "这是不正确的—— lambda 函数采用通用格式 lambda * arguments * : * expression * , 因此在这种情况下需要提供参数。" + text: " `pets_dataset.filter(lambda x ['name'].startswith ('L'))` ", + explain: "这是不正确的——lambda 函数通常的格式为: `lambda *arguments*:*expression*` , 因此在这种情况下需要提供 arguments。" }, { - text: "创建一个类似于 def filter_names(x) : return x['name'].startswith('L') 的函数并运行 pets_dataset.filter(filter_names) 。", - explain: "正确!就像使用 Dataset.map() 一样,你可以将显式函数传递给 Dataset.filter() 。当你有一些不适合于简短 lambda 函数的复杂逻辑时,这是非常有用的。其他解决方案中还有哪一个可行?", + text: "创建一个类似于 `def filter_names (x) : return x['name'].startswith ('L')` 的函数,然后运行 `pets_dataset.filter(filter_names)` 。", + explain: "正确!就像使用 `Dataset.map ()` 一样,你可以将函数传递给 `Dataset.filter ()` 。当你有一些不适合 lambda 函数的复杂逻辑时,这很有用。其他解决方案中还有哪一个可行?", correct: true } ]} /> -### 4.什么是内存映射? +### 4.什么是内存映射? -### 5.下列哪一项是内存映射的主要好处? +### 5.下列哪一项是内存映射的主要好处? -### 6.为什么下面的代码是错误的? +### 6.为什么下面的代码是错误的? ```py from datasets import load_dataset @@ -130,92 +130,92 @@ dataset[0] choices={[ { text: "它试图对一个太大而无法放入 RAM 的数据集进行流式处理。", - explain: "这是不正确的---- 流数据集是动态解压的, 你可以用非常小的RAM处理TB大小的数据集!", + explain: "这是不正确的—— 流式处理数据集时是动态解压的,你可以用非常小的 RAM 处理 TB 量级的数据集!", }, { - text: "它尝试访问 IterableDataset 。", - explain: "正确! IterableDataset 是一个生成器, 而不是一个容器, 因此你应该使用 next(iter(dataset)) 来访问它的元素。", + text: "它尝试访问 `IterableDataset` 。", + explain: "正确! `IterableDataset` 是一个生成器,而不是一个容器,因此你应该使用 `next(iter(dataset))` 来访问它的元素。", correct: true }, { - text: "数据集 allocine 没有分割train。", - explain: "这是不正确的---- 查看 Hub 上的[allocine数据集卡](https://huggingface.co/datasets/allocine), 看看它包含哪些拆分。" + text: "数据集 `allocine` 没有 `train` 部分。", + explain: "这是不正确的—— 查看 Hub 上的 [`allocine`数据卡片](https://huggingface.co/datasets/allocine) , 可以看到它有那几部分" } ]} /> -### 7.创建数据集卡的主要好处是什么? +### 7.创建数据集卡片的主要好处是什么? -### 8.什么是语义搜索? +### 8.什么是语义搜索? -### 9.对于非对称语义搜索,通常有: +### 9.对于非对称语义搜索,通常有: -### 10.我可以使用数据集加载数据用于其他领域,如语音处理? +### 10.我可以使用🤗 Datasets 来加载用于其他领域(如语音处理)的数据吗? diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx index 077ee3277..960160671 100644 --- a/chapters/zh-CN/chapter6/1.mdx +++ b/chapters/zh-CN/chapter6/1.mdx @@ -5,15 +5,15 @@ classNames="absolute z-10 right-0 top-0" /> -在 [第三章] (/course/chapter3) 中,我们研究了如何在给定任务上微调模型。 当我们这样做时,我们需要使用与模型预训练相同的标记器——但是当我们想从头开始训练模型时该怎么办? 不过,使用在来自其他领域或语言的语料库上预训练的标记器通常不是最理想的。 例如,在英语语料库上训练的标记器在日语文本语料库上表现不佳,因为两种语言中空格和标点符号的使用非常不同。 +在 [第三章](/course/chapter3) 中,我们研究了如何在特定任务上微调模型。当我们需要微调模型时,我们需要使用与模型预训练相同的 tokenizer —— 但是当我们想从头开始训练模型时应该选用哪个 tokenizer ?使用在来自其他领域或语言的语料库上预训练的 tokenizer 通常不是最理想的。例如,在英语语料库上训练的 tokenizer 在日语文本语料库上效果会大打折扣,因为两种语言在空格和标点的使用上有着显著的差异。 -在本章中,您将学习如何在文本语料库上训练一个全新的标记器,然后将其用于预训练语言模型。 这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库在 [🤗 Transformers](https://github.com /huggingface/transformers) 库之内。 我们将仔细研究这个库提供的功能,并探讨快速标记器与“慢”版本的区别。 +在本章中,你将学习如何在一份文本语料库上训练一个全新的 tokenizer,然后将使用它来预训练语言模型。这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库提供了 [🤗 Transformers](https://github.com/huggingface/transformers) 库中的“快速” tokenizer 。 我们将深入探讨这个库所提供的功能,并研究“快速” tokenizer 与“慢速”版本的区别。 -我们将涵盖的主题包括: +本章将涵盖以下主题: -* 如何训练一个新的标记器,类似于给定检查点在新的文本语料库上使用的标记器 -* 快速标记器的特殊功能 -* 目前 NLP 中使用的三种主要子词标记化算法之间的差异 -* 如何使用🤗 Tokenizers 库从头开始构建标记器并在一些数据上对其进行训练 +* 如何在新的文本语料库上训练一个类似于给定 checkpoint 所使用的新 tokenizer +* 快速 tokenizer 的特殊功能 +* 目前 NLP 中使用的三种主要子词 tokenization 算法之间的差异 +* 如何使用🤗 Tokenizers 库从头开始构建 tokenizer 并在一些数据上进行训练 -本章介绍的技术将使您为 [第 7 章](/course/chapter7/6) 中的部分做好准备,在那部分中,我们着眼于为 Python 源代码创建语言模型。 让我们首先看一下什么是“训练”标记器? \ No newline at end of file +本章介绍的技术将使你为 [第七章](/course/chapter7/6) 中的部分做好准备,在那部分中,我们将尝试为 Python 源代码创建语言模型。让我们首先看一下什么是“训练” tokenizer \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/10.mdx b/chapters/zh-CN/chapter6/10.mdx index b1b485952..4cd7b16ef 100644 --- a/chapters/zh-CN/chapter6/10.mdx +++ b/chapters/zh-CN/chapter6/10.mdx @@ -1,4 +1,4 @@ - + # 章末小测验 [[章末小测验]] @@ -7,229 +7,230 @@ classNames="absolute z-10 right-0 top-0" /> -让我们测试一下您在本章中学到了什么! +让我们测试一下你在本章中学到了什么! -### 1.你应该什么时候训练一个新的标记器? +### 1.在什么时候你应该考虑训练一个新的 tokenizer ? -### 2.当使用“ train_new_from_iterator()”时,使用文本列表生成器与文本列表相比有什么优点? +### 2.当使用 `train_new_from_iterator()` 时,使用文本列表生成器与文本列表相比有什么优点? train_new_from_iterator() 接受的唯一类型。", + text: "文本列表生成器是`train_new_from_iterator()`方法唯一接受的输入类型。", explain: "文本列表是一种特殊的文本列表生成器,因此该方法也会接受这种方法。再试一次!" }, { - text: "您将避免立即将整个数据集载入内存中。", - explain: "没错!每一批文本都会在你迭代的时候从内存中释放出来,如果你使用数据集存储文本的话,增益将尤其明显。", + text: "你可以避免一次性将整个数据集加载到内存中。", + explain: "正确!每个 batch 的文本在迭代时都将从内存中释放,如果你使用🤗 Datasets 存储你的文本,你会看到特别明显的收益。", correct: true }, { - text: "这将允许 Tokenizers 库使用并行处理。", - explain: "不,无论如何它都将使用并行处理。" + text: "这将允许🤗 Tokenizers 库使用多进程。", + explain: "不,即使随时文本列表它也会使用多进程。" }, { - text: "你训练的标记器将产生更好的文本。", - explain: "Tokenizer 不生成文本——您是否将其与语言模型混淆了?" + text: "你训练的 Tokenizer 将生成效果更好的输出。", + explain: "Tokenizer 不生成文本——你是否将其与语言模型混淆了?" } ]} /> -### 3.使用“快速”标记器的优点是什么? +### 3.使用“快速” tokenizer 有什么优势? + -### 4.“token-classification”管道如何处理跨多个标记的实体? +### 4. `token-classification` 管道如何处理跨越多个 tokens 的实体? -### 5.“question-answering”流水线如何处理长上下文? +### 5. `question-answering` 管道如何处理长上下文? -### 6.什么是标准化? +### 6.什么是标准化? -### 7.什么是子词标记化的前标记化? +### 7.什么是 tokenizer 的预分词? -### 8.选择描述标记化 BPE 模式最准确的句子。 +### 8.选择描述 BPE 算法最准确的句子。 -### 9.选择适用于 WordPiece 标记模型的句子。 +### 9.选择描述 WordPiece 算法最准确的句子。 -### 10.选择适用于 Unigram 标记模式的句子。 +### 10.选择描述 Unigram 算法最准确的句子。 diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx index 87589bd04..918d0c2f4 100644 --- a/chapters/zh-CN/chapter6/2.mdx +++ b/chapters/zh-CN/chapter6/2.mdx @@ -1,4 +1,4 @@ -# 根据已有的tokenizer训练新的tokenizer [[根据已有的tokenizer训练新的tokenizer]] +# 基于已有的 tokenizer 训练新的 tokenizer [[基于已有的 tokenizer 训练新的 tokenizer]] -如果您感兴趣的语言中没有可用的语言模型,或者如果您的语料库与您的语言模型所训练的语料库有很大不同,您很可能希望从适合您的数据的标记器从头开始重新训练模型 . 这将需要在您的数据集上训练一个新的标记器。 但这究竟是什么意思? 当我们在 [第二章](/course/chapter2) 中第一次查看标记器时,我们看到大多数 Transformer 模型使用_子词分词算法_。 为了识别哪些子词是感兴趣的并且在手头的语料库中最常出现,标记器需要仔细查看语料库中的所有文本——我们称之为*training*的过程。 这种训练的确切规则取决于所使用的标记器的类型,我们将在本章后面讨论三种主要算法。 +如果你感兴趣的语言中没有可用的语言模型,或者你的语料库与语言模型训练时所使用的语料库差异很大,你可能需要从零开始重新训练一个适应你的数据的 tokenizer 模型。训练一个新的 tokenizer 是什么意思呢?从我们在 [第二章](/course/chapter2) 中第一次看到 tokenizer 开始,我们看到大多数 Transformer 模型使用 `子词分词算法` 。为了找到语料库中的常见子词,tokenizer 需要深入统计语料库中的所有文本——这个过程我们称之为 `训练 (training)` 。具体的训练规则取决于使用的 tokenizer 类型,我们将在本章后面的部分详细介绍三种主要算法。 -⚠️ 训练标记器与训练模型不同!模型训练使用随机梯度下降使每个batch的loss小一点。它本质上是随机的(这意味着在进行两次相同的训练时,您必须设置一些随机数种子才能获得相同的结果)。训练标记器是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,用于选择它们的确切规则取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,您总是会得到相同的结果。 +⚠️ 训练 tokenizer 与训练模型不同!模型训练使用随机梯度下降使每个 batch 的 loss 小一点。它本质上是随机的(这意味着在即使两次训练的参数和算法完全相同,你也必须设置一些随机数种子才能获得相同的结果)。训练 tokenizer 是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,确定的过程取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,得到的结果总是相同的。 ## 准备语料库 [[准备语料库]] -🤗 Transformers 中有一个非常简单的 API,你可以用它来训练一个新的标记器,使它与现有标记器相同的特征: **AutoTokenizer.train_new_from_iterator()** .为了复现这一点,假设我们想从头开始训练 GPT-2,但使用英语以外的语言。我们的首要任务是在训练语料库中收集该语言的大量数据。为了提供每个人都能理解的示例,我们在这里不会使用俄语或中文之类的语言,而是使用在特定领域的英语语言:Python 代码。 - -[🤗 Datasets](https://github.com/huggingface/datasets)库可以帮助我们组装一个 Python 源代码语料库。我们将使用**load_dataset()**功能下载和缓存[CodeSearchNet](https://huggingface.co/datasets/code_search_net)数据集。该数据集是为[CodeSearchNet 挑战](https://wandb.ai/github/CodeSearchNet/benchmark)而创建的并包含来自 GitHub 上开源库的数百万种编程语言的函数。在这里,我们将加载此数据集的 Python 部分: +在🤗 Transformers 中,有一个非常简单的 API 可以让你从旧的 tokenizer 训练一个新的 tokenizer 且新的 tokenizer 具有和旧 tokenizer 相同的特性,它就是: `AutoTokenizer.train_new_from_iterator()` 。为了演示这个功能,我们将尝试从零开始训练 GPT-2 模型,但是在非英语的语言上。我们首先需要做的就是在训练语料库中收集大量的目标语言数据。为了让每个人都能理解,我们不会使用俄语或汉语这样的语言,而是使用一种特殊的英语语言:Python 代码。 + [🤗 Datasets](https://github.com/huggingface/datasets) 库可以帮助我们下载一个 Python 源代码语料库。我们将使用 `load_dataset()` 功能下载和缓存 [CodeSearchNet](https://huggingface.co/datasets/code_search_net) 数据集。该数据集是为 [CodeSearchNet 挑战](https://wandb.ai/github/CodeSearchNet/benchmark) 而创建的,其中包含了 GitHub 上开源库中的数百万个函数,涵盖了多种编程语言。在这里,我们将加载这个数据集的 Python 部分: ```py from datasets import load_dataset -# This can take a few minutes to load, so grab a coffee or tea while you wait! +# 加载这个可能需要几分钟的时间,你可以趁此喝杯咖啡或茶! raw_datasets = load_dataset("code_search_net", "python") ``` -我们可以查看训练集的部分,以查看我们数据集中有哪些列: +我们可以查看训练集部分来看我们可以使用哪些列: ```py raw_datasets["train"] @@ -46,13 +45,14 @@ Dataset({ }) ``` -我们可以看到数据集将文档字符串与代码分开,并且有他们各自的标记化后的结果。 这里。 我们将只使用 `whole_func_string` 列来训练我们的标记器。 我们可以通过指定到 `train` 中的一部分来查看这些函数的一个示例: +我们可以看到数据集把函数的文档说明(func_documentation_string)与代码(func_code_string)分开保存,并提供了一个可以参考的分词后的结果(func_code_tokens)。在这里,我们仅使用 `whole_func_string` 列来训练我们的 tokenizer 我们可以通过索引来查看其中一个函数的示例: + ```py print(raw_datasets["train"][123456]["whole_func_string"]) ``` -应该打印以下内容: +应该输出以下内容: ```out def handle_simple_responses( @@ -69,16 +69,19 @@ def handle_simple_responses( return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) ``` -我们需要做的第一件事是将数据集转换为迭代器文本列表 - 例如,文本列表。使用文本列表将使我们的标记器运行得更快(训练成批文本而不是一个接一个地处理单个文本),如果我们想避免一次将所有内容都放在内存中,它应该是一个迭代器。如果你的语料库很大,你会想要利用这样一个特性:🤗 Datasets 不会将所有内容都加载到 RAM 中,而是将数据集的元素存储在磁盘上。 +我们首先需要做的是将数据集转换为一个文本列表的 `迭代器` 例如,文本列表的列表。使用文本列表会使我们的 tokenizer 运行得更快(这样可以以文本批次为单位进行训练,而不是一次处理一个文本),并且使用迭代器可以不把所有内容都加载到内存中。如果你的语料库很大,你可能会想利用🤗 Datasets 将数据集的元素存储在磁盘上分批加载,而不是将所有内容加载到 RAM 的特性。 -执行以下操作将创建一个包含 1,000 个文本的列表的列表,但会将所有内容加载到内存中: +下面的操作会创建一个由每个列包含 1000 个文本组成的文本列表,但会将所有内容加载到内存中: ```py -# Don't uncomment the following line unless your dataset is small! -# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +# 除非你的数据集很小,否则不要直接运行下面的代码! +# training_corpus = [ +# raw_datasets["train"][i: i + 1000]["whole_func_string"] +# for i in range(0, len(raw_datasets["train"]), 1000) +# ] ``` -使用 Python 生成器,我们可以避免 Python 将任何内容加载到内存中,直到真正需要为止。要创建这样的生成器,您只需要将括号替换为圆括号: +通过使用 Python 生成器,我们可以使 Python 只将正在使用的数据加载到内存中。要创建这样一个生成器,你只需要将方括号替换为圆括号: ```py training_corpus = ( @@ -87,9 +90,9 @@ training_corpus = ( ) ``` -这行代码不会获取数据集的任何元素;它只是创建了一个可以在 Python 中使用的对象 **for** 环形。文本只会在您需要时加载(即,当您处于 **for** 需要它们的循环),并且一次只会加载 1,000 个文本。这样,即使您正在处理庞大的数据集,也不会耗尽所有内存。 +这行代码不会加载数据集的任何元素;它只创建了一个你可以在 Python for 循环中使用的对象。只有当你使用它们(即,当你在 `for` 循环尝试访问他们)时,文本才会被加载,而且一次只会加载 1000 个文本。这样,即使你在处理大型数据集,也不会耗尽所有内存。 -生成器对象的问题在于它只能使用一次,每次访问它将给出下一个值。 下面是一个例子: +生成器对象的问题是它只能被使用一次。让我们尝试获取 2 次 10 个数字组成的列表: ```py gen = (i for i in range(10)) @@ -97,14 +100,14 @@ print(list(gen)) print(list(gen)) ``` -我们第一次得到了这个列表,然后是一个空列表: +我们第一次得到了这个列表,第二次我们得到了一个空列表: ```python out [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] ``` -这就是我们定义一个返回生成器的函数的原因: +这就是为什么我们需要定义一个返回生成器的函数。通过每次调用函数生成一个新的生成器对象,我们可以多次使用生成器而不会遇到只能使用一次的限制。 ```py def get_training_corpus(): @@ -117,7 +120,7 @@ def get_training_corpus(): training_corpus = get_training_corpus() ``` -您还可以在一个 **for** 循环内部使用 **yield** 关键字定义您的生成器: +你还可以在一个 `for` 循环内部使用 `yield` 关键字定义你的生成器: ```py def get_training_corpus(): @@ -127,11 +130,11 @@ def get_training_corpus(): yield samples["whole_func_string"] ``` -这将产生与以前完全相同的生成器,但允许您使用比列表生成式中更复杂的逻辑。 +这将得到和上面列表生成器完全相同的生成器,但允许你在迭代器的过程中添加更复杂的逻辑。 -## 训练一个新的标记器 [[训练一个新的标记器]] +## 训练一个新的 tokenizer [[训练一个新的 tokenizer ]] -现在我们的语料库是文本批量迭代器的形式,我们准备训练一个新的标记器。为此,我们首先需要加载要与模型配对的标记器(此处为 GPT-2): +现在我们已经将文本转化为迭代器形式准备好了我们的语料库,我们就可以开始训练新的 tokenizer 了。首先,我们需要加载我们想要与我们的模型匹配的 tokenizer (这我们这个例子中是 GPT-2): ```py from transformers import AutoTokenizer @@ -139,9 +142,9 @@ from transformers import AutoTokenizer old_tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -即使我们要训练一个新的标记器,最好还是这样做以避免完全从头开始。这样,我们就不必指定任何关于标记化算法或我们想要使用的特殊标记;我们的新标记器将与 GPT-2 完全相同,唯一会改变的是输入的数据,这将取决于我们训练的语料。 +尽管我们要训练一个新的 tokenizer,但从旧的 tokenizer 开始初始化依然是个不错的主意,这样,我们就不必指定具体的 tokenization 算法或设置我们想要使用的特殊 tokens;我们新的 tokenizer 将与 GPT-2 完全相同,唯一的区别是词汇表,这将由我们的语料库通过训练来重新确定。 -首先让我们看看这个标记器将如何处理示例的数据: +首先让我们看看旧的 tokenizer 将如何处理示例的数据: ```py example = '''def add_numbers(a, b): @@ -157,20 +160,21 @@ tokens 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -这个标记器有一些特殊的符号,比如 **Ċ** 和 **Ġ** ,分别表示空格和换行符。正如我们所看到的,这不是太有效:标记器为每个空格返回单独的标记,当它可以将缩进级别组合在一起时(因为在代码中具有四个或八个空格的集合将非常普遍)。它也有点奇怪地拆分了函数名称,而习惯使用**_**的函数命名的方法。 +这个 tokenizer 输出了一些特殊的符号,比如 `Ċ` 和 `Ġ` ,分别表示空格和换行符。正如我们所看到的,这并不是非常高效:tokenizer 将每个空格视作为单独的 token,其实它可以将缩进级别组合在一起时(因为在代码中经常出现相邻在一起的四个或八个空格)。它也有点奇怪地拆分了函数名称,对使用 `_` 命名方法的函数并不友好。 -让我们训练一个新的标记器,看看它是否能解决这些问题。为此,我们将使用 **train_new_from_iterator()** 方法: +让我们训练一个新的 tokenizer 看看它是否能解决这些问题。为此,我们将使用 `train_new_from_iterator()` 方法: ```py tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) ``` -如果您的语料库非常大,此命令可能需要一些时间,但对于这个 1.6 GB 文本数据集,它的速度非常快(在具有 12 个内核的 AMD Ryzen 9 3900X CPU 上为 1 分 16 秒)。 -注意 **AutoTokenizer.train_new_from_iterator()** 仅当您使用的标记器是“快速(fast)”标记器时才有效。正如您将在下一节中看到的,🤗 Transformers 库包含两种类型的标记器:一些完全用 Python 编写,而另一些(快速的)由 🤗 Tokenizers 库支持,该库用[Rust](https://www.rust-lang.org)编程语言编写。 Python 是最常用于数据科学和深度学习应用程序的语言,但是当需要并行化以提高速度时,必须用另一种语言编写。例如,模型计算核心的矩阵乘法是用 CUDA 编写的,CUDA 是一个针对 GPU 的优化 C 库。 +如果你的语料库非常大,此命令可能需要一些时间,但对于这个 1.6 GB 文本数据集,它的速度非常快(在具有 12 个内核的 AMD Ryzen 9 3900X CPU 上仅需 1 分 16 秒)。 + +注意 `AutoTokenizer.train_new_from_iterator()` 只有你使用的 tokenizer 是“快速(fast)” tokenizer 时才有效。下一节中,你将在看到,🤗 Transformers 库包含两种类型的 tokenizer 一些(慢速的)完全用 Python 编写,而另一些(快速的)由 🤗 Tokenizers 库支持,该库用 [Rust](https://www.rust-lang.org) 编程语言编写。Python 是最常用于数据科学和深度学习应用程序的语言,但是当需要并行化以提高速度时,就需要用另一种语言来编写。例如,模型计算核心的矩阵乘法是用 CUDA 编写的,这是一个针对 GPU 优化的 C 语言库。 -用纯 Python 训练一个全新的标记器会非常缓慢,这就是我们开发 🤗 Tokenizers库的原因。请注意,正如您无需学习 CUDA 语言即可在 GPU 上执行您的模型一样,您也无需学习 Rust 即可使用快速标记器。 🤗 Tokenizers 库为许多内部调用 Rust 代码的方法提供 Python 绑定;例如,并行化新标记器的训练,或者,正如我们在[第三章](/course/chapter3)中看到的,对一批输入进行标记化。 +用纯 Python 训练一个全新的 tokenizer 会非常缓慢,这就是我们开发 🤗 Tokenizers 库的原因。正如你无需学习 CUDA 语言即可在 GPU 上训练你的模型一样,你也无需学习 Rust 即可使用快速 tokenizer。🤗 Tokenizers 库为许多内部调用 Rust 代码的方法提供 Python 语言绑定;例如,并行化训练新的 tokenizer 或者像我们在 [第三章](/course/chapter3) 中看到的那样,对一批输入进行 tokenize。 -大多数 Transformer 模型都有可用的快速标记器(您可以[在这里](https://huggingface.co/transformers/#supported-frameworks)检查一些例外情况),如果 **AutoTokenizer** 可用,API 总是为您选择快速标记器。在下一节中,我们将看看快速标记器具有的其他一些特殊功能,这些功能对于标记分类和问答等任务非常有用。然而,在深入研究之前,让我们在上一个示例中尝试我们全新的标记器: +大多数 Transformer 模型都有可用的快速 tokenizer (你可以 [在这里](https://huggingface.co/transformers/#supported-frameworks) 检查一些例外情况),如果 `AutoTokenizer` 可用,API 默认为你选择快速 tokenizer 在下一节中,我们将看看快速 tokenizer 具有的其他一些特殊功能,这些功能对于 token 分类和问答等任务非常有用。然而,在深入研究之前,让我们尝试在之前的例子上使用我们的全新 tokenizer ```py tokens = tokenizer.tokenize(example) @@ -182,7 +186,7 @@ tokens 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -在这里我们再次看到特殊符号 **Ċ** 和 **Ġ** 表示空格和换行符,但我们也可以看到我们的标记器学习了一些高度特定于 Python 函数语料库的标记:例如,有一个 **ĊĠĠĠ** 表示缩进的标记,以及 **Ġ** 表示开始文档字符串的三个引号的标记。标记器还正确使用**_**命名的规范将函数名称拆分为 .这是一个非常紧凑的表示;相比之下,在同一个例子中使用简单的英语标记器会给我们一个更长的句子: +在这里我们再次看到了表示空格和换行符的特殊符号 `Ċ` 和 `Ġ` ,但我们也可以看到我们的 tokenizer 学习了一些专属于 Python 函数语料库的 token:例如,有一个 `ĊĠĠĠ`token 表示缩进,以及 `Ġ` token 表示开始文档字符串的三个引号。tokenizer 也正确地在 `_` 上拆分了函数名称。这是一个非常紧凑的表示;相比之下,使用简单的英语 tokenizer 会得到一个更长的句子: ```py print(len(tokens)) @@ -216,17 +220,17 @@ tokenizer.tokenize(example) 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] ``` -除了一个缩进对应的token,这里我们还可以看到一个双缩进的token: **ĊĠĠĠĠĠĠĠ** .特殊的 Python 词如 **class** , **init** , **call** , **self** , 和 **return** 每个都被标记为一个标记,我们可以看到,以及分裂 **_** 和 **.** 标记器甚至可以正确拆分驼峰式名称: **LinearLayer** 被标记为 **[ĠLinear, Layer]** . +除了与缩进对应的 token 外,这里我们还可以看到一个与双缩进对应的 token : `ĊĠĠĠĠĠĠĠ` 。特殊的 Python 关键词如 `class` , `init` , `call` , `self` ,和 `return` 每个都被分配了一个 token ,我们还可以看到,除了可以在 `_` 和 `.` 上正确拆分,tokenizer 甚至可以正确拆分驼峰法命名的名称: `LinearLayer` 被分词为 `[ĠLinear, Layer]` 。 -## 保存标记器 [[保存标记器]] +## 保存 tokenizer [[保存 tokenizer ]] -为了确保我们以后可以使用它,我们需要保存我们的新标记器。就像模型一样,是通过 **save_pretrained()** 方法: +为了确保我们以后可以使用它,我们需要保存我们的新 tokenizer 。像模型一样,是通过 `save_pretrained()` 方法进行保存: ```py tokenizer.save_pretrained("code-search-net-tokenizer") ``` -这将创建一个名为的*code-search-net-tokenizer*的新文件夹,它将包含重新加载标记器所需要的所有文件。如果您想与您的同事和朋友分享这个标记器,您可以通过登录您的帐户将其上传到 Hub。如果您在notebook上工作,有一个方便的功能可以帮助您: +这将创建一个名为的 `code-search-net-tokenizer` 的新文件夹,它将包含重新加载 tokenizer 所需要的所有文件。如果你想与你的同事和朋友分享这个 tokenizer 你可以通过登录你的帐户将其上传到 Hub。如果你在 notebook 上工作,有一个便捷的功能可以帮助你: ```python from huggingface_hub import notebook_login @@ -234,23 +238,23 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。如果您不是在notebook上工作,只需在终端中输入以下行: +这将显示一个小部件,你可以在其中输入你的 Hugging Face 账号密码。如果你不是在 notebook 上工作,只需在终端中输入以下行: ```bash huggingface-cli login ``` -登录后,您可以通过执行以下命令来推送您的标记器: +登录后,你可以通过执行以下命令来推送你的 tokenizer ```py tokenizer.push_to_hub("code-search-net-tokenizer") ``` -这将在您的命名空间中创建一个名为**code-search-net-tokenizer**的新存储库 ,包含标记器文件。然后,您可以使用以下命令从任何地方加载标记器的 **from_pretrained()** 方法: +这将在你的账户中创建一个名为 `code-search-net-tokenizer` 的新仓库,其中将包含 tokenizer 文件。然后,你可以使用 tokenizer 的 `from_pretrained()` 方法从任何地方加载 tokenizer 。 ```py -# Replace "huggingface-course" below with your actual namespace to use your own tokenizer +# 将下面的 "huggingface-course" 替换为你的用户名来加载你的 tokenizer tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -您现在已准备好从头开始训练语言模型并根据您手头的任务对其进行微调!我们将在[第七章](/course/chapter7)进行这部分。但首先,在本章的其余部分,我们将仔细研究快速标记器,并详细探讨调用 **train_new_from_iterator()** 方法时实际发生的情况 . +你现在已准备好从头开始训练语言模型并根据你手头的任务对其进行微调!我们将在 [第七章](/course/chapter7) 进行这部分。在本章的剩余部分,我们将仔细研究快速 tokenizer 并详细探讨调用 `train_new_from_iterator()` 方法时到底在幕后发生了什么。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index 2cdcc95e3..1a9fea14a 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -1,6 +1,6 @@ -# 快速标记器的特殊能力 [[快速标记器的特殊能力]] +# 快速 tokenizer 的特殊能力 [[快速 tokenizer 的特殊能力]] {#if fw === 'pt'} @@ -22,20 +22,18 @@ {/if} -在本节中,我们将仔细研究 🤗 Transformers 中标记器的功能。到目前为止,我们只使用它们来标记输入或将 ID 解码回文本,但是标记器——尤其是那些由 🤗 Tokenizers 库支持的——可以做更多的事情。为了说明这些附加功能,我们将探索如何重现结果 **token-classification** (我们称之为 **ner** ) 和 **question-answering** 我们第一次在[Chapter 1](/course/chapter1)中遇到的管道. +在本节中,我们将仔细研究 🤗 Transformers 中 tokenizer 的功能。到目前为止,我们只使用它们来对文本进行 tokenize 或将token ID 解码回文本,但是 tokenizer —— 特别是由🤗 Tokenizers 库支持的 tokenizer —— 能够做的事情还有很多。为了说明这些附加功能,我们将探讨如何复现在 [第一章](/course/chapter1) 中首次遇到的 `token-classification` (我们称之为 `ner` ) 和 `question-answering` 管道的结果。 -在接下来的讨论中,我们会经常区分“慢”和“快”分词器。慢速分词器是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 分词器提供的,它们是用 Rust 编写的。如果你还记得在[Chapter 5](/course/chapter5/3)中报告了快速和慢速分词器对药物审查数据集进行分词所需的时间的这张表,您应该知道为什么我们称它们为“快”和“慢”: +在接下来的讨论中,我们会经常区分“慢速”和“快速” tokenizer 。慢速 tokenizer 是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 Tokenizers 提供的,它们是用 Rust 编写的。如果你还记得在 [第五章](/course/chapter5/3) 中对比了快速和慢速 tokenizer 对药物审查数据集进行 tokenize 所需的时间的这张表,你应该理解为什么我们称它们为“快速”和“慢速”: -| | Fast tokenizer | Slow tokenizer -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s +| | 快速 tokenizer | 慢速 tokenizer +:--------------:|:--------------:|:-------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s -⚠️ 对单个句子进行分词时,您不会总是看到相同分词器的慢速和快速版本之间的速度差异。事实上,快速版本实际上可能更慢!只有同时对大量文本进行标记时,您才能清楚地看到差异。 +⚠️ 对单个句子进行 tokenize 时,你不总是能看到同一个 tokenizer 的慢速和快速版本之间的速度差异。事实上,快速版本可能更慢!只有同时对大量文本进行 tokenize 时,你才能清楚地看到差异。 @@ -43,9 +41,11 @@ -分词器的输出不是简单的 Python 字典;我们得到的实际上是一个特殊的 **BatchEncoding** 目的。它是字典的子类(这就是为什么我们之前能够毫无问题地索引到该结果中的原因),但具有主要由快速标记器使用的附加方法。 +tokenizer 的输出不是简单的 Python 字典;我们得到的实际上是一个特殊的 `BatchEncoding` 对象。它是字典的子类(这就是为什么我们之前能够直接使用索引获取结果的原因),但是它还提供了一些主要由快速 tokenizer 提供的附加方法。 -除了它们的并行化能力之外,快速标记器的关键功能是它们始终跟踪最终标记来自的原始文本范围——我们称之为偏移映射.这反过来又解锁了诸如将每个单词映射到它生成的标记或将原始文本的每个字符映射到它内部的标记等功能,反之亦然。让我们看一个例子: +除了它们的并行化能力之外,快速 tokenizer 的关键功能是它们始终跟踪最终 token 相对于的原始文本的映射——我们称之为 `偏移映射(offset mapping)` 。这反过来又解锁了如将每个词映射到它生成的 token,或者将原始文本的每个字符映射到它所在的 token 等功能。 + +让我们看一个例子: ```py from transformers import AutoTokenizer @@ -56,13 +56,13 @@ encoding = tokenizer(example) print(type(encoding)) ``` -如前所述,我们得到一个 **BatchEncoding** 标记器输出中的对象: +如前所述,我们从 tokenizer 得到了一个 `BatchEncoding` 对象: ```python out ``` -由于 **AutoTokenizer** 类默认选择快速标记器,我们可以使用附加方法 this **BatchEncoding** 对象提供。我们有两种方法来检查我们的分词器是快的还是慢的。我们可以检查 **is_fast** 的属性 **tokenizer** : +由于 `AutoTokenizer` 类默认选择快速 tokenizer 因此我们可以使用 `BatchEncoding` 对象提供的附加方法。我们有两种方法来检查我们的 tokenizer 是快速的还是慢速的。我们可以检查 `tokenizer` 的 `is_fast` 属性: ```python tokenizer.is_fast @@ -72,7 +72,7 @@ tokenizer.is_fast True ``` -或检查我们的相同属性 **encoding** : +或检查我们 `encoding` 的 `is_fast` 属性: ```python encoding.is_fast @@ -82,7 +82,7 @@ encoding.is_fast True ``` -让我们看看快速标记器使我们能够做什么。首先,我们可以访问令牌而无需将 ID 转换回令牌: +让我们看看快速 tokenizer 能让为我们提供什么功能。首先,我们可以直接得到Ttokenization 之前的单词而无需将 ID 转换回单词: ```py encoding.tokens() @@ -93,7 +93,7 @@ encoding.tokens() 'Brooklyn', '.', '[SEP]'] ``` -在这种情况下,索引 5 处的令牌是 **##yl** ,它是原始句子中“Sylvain”一词的一部分。我们也可以使用 **word_ids()** 获取每个标记来自的单词索引的方法: +在这种情况下,索引 5 处的 token 是 `##yl` ,它是原始句子中“Sylvain”一词的一部分。我们也可以使用 `word_ids()` 方法来获取每个 token 原始单词的索引: ```py encoding.word_ids() @@ -103,19 +103,19 @@ encoding.word_ids() [None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] ``` -我们可以看到分词器的特殊标记 **[CLS]** 和 **[SEP]** 被映射到 **None** ,然后每个标记都映射到它起源的单词。这对于确定一个标记是否在单词的开头或两个标记是否在同一个单词中特别有用。我们可以依靠 **##** 前缀,但它仅适用于类似 BERT 的分词器;这种方法适用于任何类型的标记器,只要它是快速的。在下一章中,我们将看到如何使用此功能将每个单词的标签正确应用于命名实体识别 (NER) 和词性 (POS) 标记等任务中的标记。我们还可以使用它来屏蔽来自屏蔽语言建模中来自同一单词的所有标记(一种称为全词掩码)。 +我们可以看到 tokenizer 的特殊 token `[CLS]` 和 `[SEP]` 被映射到 `None` ,然后每个 token 都映射到它来源的单词。这对于确定一个 token 是否在单词的开头或两个 token 是否在同一个单词中特别有用。对于 BERT 类型(BERT-like)的的 tokenizer 我们也可以依靠 `##` 前缀来实现这个功能;不过只要是快速 tokenizer 它所提供的 `word_ids()` 方法适用于任何类型的 tokenizer 。在下一章,我们将看到如何利用这种能力,将我们为每个词正确地对应到词汇任务中的标签,如命名实体识别(NER)和词性标注(POS)。我们也可以使用它在掩码语言建模(masked language modeling)中来遮盖来自同一词的所有 token(一种称为 `全词掩码(whole word masking)` 的技术)。 -一个词是什么的概念很复杂。例如,“I'll”(“I will”的缩写)算一两个词吗?它实际上取决于分词器和它应用的预分词操作。一些标记器只是在空格上拆分,因此他们会将其视为一个词。其他人在空格顶部使用标点符号,因此将其视为两个词。 +词的概念是复杂的。例如,“I'll”(“I will”的缩写)算作一个词还是两个词?这实际上取决于 tokenizer 和它采用的预分词操作。有些 tokenizer 只在空格处分割,所以它们会把这个看作是一个词。有些其他 tokenizer 在空格的基础之上还使用标点,所以会认为它是两个词。 -✏️ 试试看!从bert base cased和roberta base检查点创建一个标记器,并用它们标记“81s”。你观察到了什么?ID这个词是什么? +✏️ **试试看!**从 `bert base cased` 和 `roberta base` checkpoint 创建一个 tokenizer 并用它们对“81s”进行分词。你观察到了什么?这些词的 ID 是什么? -同样,有一个 **sentence_ids()** 我们可以用来将标记映射到它来自的句子的方法(尽管在这种情况下, **token_type_ids** 分词器返回的信息可以为我们提供相同的信息)。 +同样,我们还有一个 `sentence_ids()` 方法,可以用它把一个 token 映射到它原始的句子(尽管在这种情况下,tokenizer 返回的 `token_type_ids`也可以为我们提供相同的信息)。 -最后,我们可以将任何单词或标记映射到原始文本中的字符,反之亦然,通过 **word_to_chars()** 或者 **token_to_chars()** 和 **char_to_word()** 或者 **char_to_token()** 方法。例如, **word_ids()** 方法告诉我们 **##yl** 是索引 3 处单词的一部分,但它是句子中的哪个单词?我们可以这样发现: +最后,我们可以通过 `word_to_chars()` 或 `token_to_chars()` 和 `char_to_word()` 或 `char_to_token()` 方法,将任何词或 token 映射到原始文本中的字符,反之亦然。例如, `word_ids()` 方法告诉我们 `##yl` 是索引 3 处单词的一部分,但它是句子中的哪个单词?我们可以这样找出来: ```py start, end = encoding.word_to_chars(3) @@ -126,17 +126,18 @@ example[start:end] Sylvain ``` -正如我们之前提到的,这一切都是由快速标记器跟踪每个标记来自列表中的文本跨度这一事实提供支持的抵消.为了说明它们的用途,接下来我们将向您展示如何复制结果 **token-classification** 手动管道。 +如前所述,这一切都是由于快速分词器跟踪每个 token 来自的文本范围的一组*偏移*。为了阐明它们的作用,接下来我们将展示如何手动复现 `token-classification` 管道的结果。 + -✏️ 试试看!创建您自己的示例文本,看看您是否能理解哪些标记与单词 ID 相关联,以及如何提取单个单词的字符跨度。对于奖励积分,请尝试使用两个句子作为输入,看看句子 ID 是否对您有意义。 +✏️ **试试看!** 使用自己的文本,看看你是否能理解哪些 token 与单词 ID 相关联,以及如何提取单个单词的字符跨度。附加题:请尝试使用两个句子作为输入,看看句子 ID 是否对你有意义。 -## 在令牌分类管道内 [[在令牌分类管道内]] +## `token-classification` 管道内部流程 [[`token-classification`管道内部流程]] -在[Chapter 1](/course/chapter1)我们第一次尝试使用 NER——任务是识别文本的哪些部分对应于个人、地点或组织等实体——使用 🤗 Transformers **pipeline()** 功能。然后,在[Chapter 2](/course/chapter2),我们看到了管道如何将从原始文本中获取预测所需的三个阶段组合在一起:标记化、通过模型传递输入和后处理。前两步 **token-classification** 管道与任何其他管道相同,但后处理稍微复杂一些 - 让我们看看如何! +在 [第一章](/course/chapter1) 我们初次尝试实现命名实体识别(NER)——该任务是确定文本的哪些部分对应于人名、地名或组织名等实体——当时是使用🤗 Transformers 的 `pipeline()` 函数实现的。然后,在 [第二章](/course/chapter2) ,我们看到一个管道如何将获取原始文本到预测结果的三个阶段整合在一起:tokenize、通过模型处理输入和后处理。 `token-classification` 管道中的前两步与其他任何管道中的步骤相同,但后处理稍有复杂——让我们看看具体情况! {#if fw === 'pt'} @@ -148,9 +149,9 @@ Sylvain {/if} -### 通过管道获得基本结果 [[通过管道获得基本结果]] +### 使用管道获得基本结果 [[使用管道获得基本结果]] -首先,让我们获取一个标记分类管道,以便我们可以手动比较一些结果。默认使用的模型是[dbmdz/bert-large-cased-finetuned-conll03-english](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english);它对句子执行 NER: +首先,让我们获取一个 token 分类管道,以便我们可以手动比较一些结果。这次我们选用的模型是 [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ;我们使用它对句子进行 NER: ```py from transformers import pipeline @@ -170,7 +171,7 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -该模型正确地将“Sylvain”生成的每个标记识别为一个人,将“Hugging Face”生成的每个标记识别为一个组织,将“Brooklyn”生成的标记识别为一个位置。我们还可以要求管道将对应于同一实体的令牌组合在一起: +模型正确地识别出:“Sylvain”是一个人,“Hugging Face”是一个组织,以及“Brooklyn”是一个地点。我们也可以让管道将同一实体的 token 组合在一起: ```py from transformers import pipeline @@ -185,21 +186,19 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -**aggregation_strategy** 选择将更改为每个分组实体计算的分数。和 **simple** 分数只是给定实体中每个标记的分数的平均值:例如,“Sylvain”的分数是我们在前面的示例中看到的标记分数的平均值 **S** , **##yl** , **##va** , 和 **##in** .其他可用的策略是: - -- `"first"`, 其中每个实体的分数是该实体的第一个标记的分数(因此对于“Sylvain”,它将是 0.993828,标记的分数) +选择不同的 `aggregation_strategy` 可以更改每个分组实体计算的策略。对于 `simple` 策略,最终的分数就是给定实体中每个 token 的分数的平均值:例如,“Sylvain”的分数是我们在前一个例子中看到的 token `S` , `##yl` , `##va` ,和 `##in` 的分数的平均值。其他可用的策略包括: -- `"max"`,其中每个实体的分数是该实体中标记的最大分数(因此对于“Hugging Face”,它将是 0.98879766,即“Face”的分数) +- “first”,其中每个实体的分数是该实体的第一个 token 的分数(因此对于“Sylvain”,分数将是 0.993828,这是“S”的分数) +- “max”,其中每个实体的分数是该实体中 token 的最大分数(因此对于“Hugging Face”,分数将是 0.98879766,即“Face”的分数) +- “average”,其中每个实体的分数是组成该实体的单词分数的平均值(因此对于“Sylvain”,与“simple”策略相同,但“Hugging Face”的得分将是 0.9819,这是“Hugging”的分数 0.975 和“Face”的分数 0.98879 的平均值) -- `"average"`, 其中每个实体的分数是组成该实体的单词分数的平均值(因此对于“Sylvain”,与“simple”策略,但“Hugging Face”的得分为 0.9819,“Hugging”得分的平均值为 0.975,“Face”得分为 0.98879) - -现在让我们看看如何在不使用pipeline()函数的情况下获得这些结果! +现在让我们看看如何在不使用 `pipeline()` 函数的情况下获得这些结果! ### 从输入到预测 [[从输入到预测]] {#if fw === 'pt'} -首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **AutoXxx** 类,然后在我们的示例中使用它们: +首先,我们需要将我们的输入进行 tokenization 并将其传递给模型。这个过程与 [第二章](/course/chapter2) 中的方法完全相同;我们使用 `AutoXxx` 类实例化 tokenizer 和模型,然后将我们的示例传递给它们: ```py from transformers import AutoTokenizer, AutoModelForTokenClassification @@ -213,7 +212,7 @@ inputs = tokenizer(example, return_tensors="pt") outputs = model(**inputs) ``` -由于我们正在使用 **AutoModelForTokenClassification** 在这里,我们为输入序列中的每个标记获得一组 logits: +由于我们在此使用了 `AutoModelForTokenClassification` ,所以我们得到了输入序列中每个 token 的一组 logits: ```py print(inputs["input_ids"].shape) @@ -227,7 +226,7 @@ torch.Size([1, 19, 9]) {:else} -首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **AutoXxx** 类,然后在我们的示例中使用它们: +首先,我们需要我们的输入 tokenization 并将其传递给模型。这个过程与 [第二章](/course/chapter2) 中的方法完全相同;我们使用 `TFAutoXxx` 类实例化 tokenizer 和模型,然后将我们的示例传递给它们: ```py from transformers import AutoTokenizer, TFAutoModelForTokenClassification @@ -241,7 +240,7 @@ inputs = tokenizer(example, return_tensors="tf") outputs = model(**inputs) ``` -于我们正在使用 **AutoModelForTokenClassification** 在这里,我们为输入序列中的每个标记获得一组 logits: +由于我们在此使用了 `TFAutoModelForTokenClassification` ,所以我们得到了输入序列中每个 token 的一组 logits: ```py print(inputs["input_ids"].shape) @@ -255,7 +254,7 @@ print(outputs.logits.shape) {/if} -我们有一个包含 19 个标记的 1 个序列的批次,模型有 9 个不同的标签,因此模型的输出具有 1 x 19 x 9 的形状。与文本分类管道一样,我们使用 softmax 函数来转换这些 logits到概率,我们采用 argmax 来获得预测(请注意,我们可以在 logits 上采用 argmax,因为 softmax 不会改变顺序): +我们有一个包含 19 个 token 序列的 batch 和有 9 个不同的标签类型,所以模型的输出形状为 1 x 19 x 9。像文本分类管道一样,我们使用 softmax 函数将这些 logits 转化为概率,并取 argmax 来得到预测(请注意,我们可以在 logits 上直接算取 argmax,因为 softmax 不会改变顺序): {#if fw === 'pt'} @@ -285,7 +284,7 @@ print(predictions) [0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] ``` - **model.config.id2label** 属性包含索引到标签的映射,我们可以用它来理解预测: +`model.config.id2label` 属性包含索引到标签的映射,我们可以用它来将预测转化为标签: ```py model.config.id2label @@ -303,16 +302,16 @@ model.config.id2label 8: 'I-LOC'} ``` -正如我们之前看到的,有 9 个标签: **O** 是不在任何命名实体中的标记的标签(它代表“外部”),然后我们为每种类型的实体(杂项、人员、组织和位置)提供两个标签。标签 **B-XXX** 表示令牌在实体的开头 **XXX** 和标签 **I-XXX** 表示令牌在实体内 **XXX** .例如,在当前示例中,我们希望我们的模型对令牌进行分类 **S** 作为 **B-PER** (一个人实体的开始)和令牌 **##yl** , **##va** 和 **##in** 作为 **I-PER** (在个人实体内) +如前所述,这里有 9 个标签: `O` 是不在任何实体中的 token 的标签(它代表“outside”),然后我们为每种类型的实体(杂项、人员、组织和位置)提供两个标签:标签 `B-XXX` 表示 token 在实体 `XXX` 的开头,标签 `I-XXX` 表示 token 在实体 `XXX` 内部。例如,在当前的例子,我们期望我们的模型将 token `S` 分类为 `B-PER` (人物实体的开始),并且将 token `##yl` , `##va` 和 `##in` 分类为 `I-PER` (人物实体的内部) -在这种情况下,您可能认为模型是错误的,因为它给出了标签 **I-PER** 对所有这四个令牌,但这并不完全正确。实际上有两种格式 **B-** 和 **I-** 标签:IOB1和IOB2. IOB2 格式(下面粉红色)是我们介绍的格式,而在 IOB1 格式(蓝色)中,标签以 **B-** 仅用于分隔相同类型的两个相邻实体。我们使用的模型在使用该格式的数据集上进行了微调,这就是它分配标签的原因 **I-PER** 到 **S** 令牌。 +你可能会觉得上面模型的输出是错误的,因为它给所有这四个 token 都标上了 `I-PER` 标签,但这样理解并不完全正确。对于 `B-` 和 `I-` 标签,实际上有两种格式:IOB1 和 IOB2。我们介绍的是 IOB2 格式(如下图所示的粉色),而在 IOB1 格式(蓝色)中,以 `B-` 开头的标签只用于分隔同一类型的两个相邻实体。我们正在使用的模型在使用该格式的数据集上进行了微调,这就是为什么它将 `S` token 标上了 `I-PER` 标签的原因。
IOB1 vs IOB2 format
-了这张地图,我们已经准备好(几乎完全)重现第一个管道的结果——我们可以获取每个未被归类为的标记的分数和标签 **O** : +有了这个映射字典,我们就可以几乎完全复现管道的结果 —— 我们只需要获取每个没有被分类为 O 的 token 的得分和标签: ```py results = [] @@ -339,7 +338,7 @@ print(results) {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] ``` -这与我们之前的情况非常相似,只有一个例外:管道还为我们提供了有关 **start** 和 **end** 原始句子中的每个实体。这是我们的偏移映射将发挥作用的地方。要获得偏移量,我们只需要设置 **return_offsets_mapping=True** 当我们将分词器应用于我们的输入时: +这与我们之前的结果非常相似,但有一点不同:pipeline 还给我们提供了每个实体在原始句子中的 `start` 和 `end` 的信息。如果要复现这个特性,这就是我们的偏移映射要发挥作用的地方。要获得偏移量,我们只需要在使用 tokenizer 器时设置 `return_offsets_mapping=True` : ```py inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) @@ -351,20 +350,20 @@ inputs_with_offsets["offset_mapping"] (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] ``` -每个元组是对应于每个标记的文本跨度,其中 **(0, 0)** 保留用于特殊令牌。我们之前看到索引 5 处的令牌是 **##yl** , 其中有 **(12, 14)** 作为这里的抵消。如果我们在示例中抓取相应的切片: +每个元组都是每个 token 对应的文本范围,其中 `(0, 0)` 是为特殊 token 保留的。我们之前看到索引 5 的 token 是 `##yl` ,它所对应的偏移量是 `(12, 14)` 。如果我们截取我们例子中的对应片段: ```py example[12:14] ``` -我们得到了正确的文本跨度,而没有 **##** : +我们会得到没有 `##` 的文本: ```python out yl ``` -使用这个,我们现在可以完成之前的结果: +使用这个,我们现在可以完成之前的想法: ```py results = [] @@ -400,13 +399,14 @@ print(results) {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -这和我们从第一个管道中得到的一样! +我们得到了与第一条 pipeline 相同的结果! + +### 实体分组 [[实体分组]] -### 分组实体 [[分组实体]] +使用偏移来确定每个实体的开始和结束的索引很方便,但这并不是它唯一的用法。当我们希望将实体分组在一起时,偏移映射将为我们省去很多复杂的代码。例如,如果我们想将 `Hu` 、 `##gging` 和 `Face` token 分组在一起,我们可以制定特殊规则,比如说前两个应该在去除 `##` 的同时连在一起, `Face` 应该在不以 `##` 开头的情况下增加空格 —— 但这些规则只适用于这种特定类型的分词器。当我们使用 SentencePiece 或 Byte-Pair-Encoding 分词器(在本章后面讨论)时就要重新写另外一套规则。 -使用偏移量来确定每个实体的开始和结束键很方便,但该信息并不是绝对必要的。然而,当我们想要将实体组合在一起时,偏移量将为我们节省大量混乱的代码。例如,如果我们想将令牌组合在一起 **Hu** , **##gging** , 和 **Face** ,我们可以制定特殊的规则,说前两个应该附加,同时删除 **##** ,以及 **Face** 应该添加一个空格,因为它不以 **##** — 但这仅适用于这种特定类型的标记器。我们必须为 SentencePiece 或 Byte-Pair-Encoding 分词器(本章稍后讨论)。 +有了偏移量,就可以免去为特定分词器定制特殊的分组规则:我们只需要取原始文本中以第一个 token 开始和最后一个 token 结束的范围。因此,假如说我们有 `Hu` 、 `##gging` 和 `Face` token,我们只需要从字符 33( `Hu` 的开始)截取到字符 45( `Face` 的结束): -编写另一组规则。使用偏移量,所有自定义代码都消失了:我们可以在原始文本中获取从第一个标记开始到最后一个标记结束的跨度。所以,在令牌的情况下 **Hu** , **##gging** , 和 **Face** ,我们应该从字符 33(开始 **Hu** ) 并在字符 45 之前结束(结束 **Face** ): ```py example[33:45] @@ -416,7 +416,7 @@ example[33:45] Hugging Face ``` -为了编写在对实体进行分组的同时对预测进行后处理的代码,我们将连续并标记为的实体分组在一起 **I-XXX** ,除了第一个,可以标记为 **B-XXX** 或者 **I-XXX** (因此,当我们得到一个实体时,我们停止对实体进行分组 **O** ,一种新型实体,或 **B-XXX** 这告诉我们一个相同类型的实体正在启动): +为了编写处理预测结果并分组实体的代码,我们将对连续标记为 `I-XXX` 的实体进行分组,因为只有实体的第一个 token 可以被标记为 `B-XXX` 或 `I-XXX` ,因此,当我们遇到实体 `O` 、新类型的实体或 `B-XXX` 时,我们就可以停止聚合同一类型实体。 ```py import numpy as np @@ -431,11 +431,11 @@ while idx < len(predictions): pred = predictions[idx] label = model.config.id2label[pred] if label != "O": - # Remove the B- or I- + # 删除 B- 或者 I- label = label[2:] start, _ = offsets[idx] - # Grab all the tokens labeled with I-label + # 获取所有标有 I 标签的token all_scores = [] while ( idx < len(predictions) @@ -445,7 +445,7 @@ while idx < len(predictions): _, end = offsets[idx] idx += 1 - # The score is the mean of all the scores of the tokens in that grouped entity + # 分数是该分组实体中所有token分数的平均值 score = np.mean(all_scores).item() word = example[start:end] results.append( @@ -462,7 +462,7 @@ while idx < len(predictions): print(results) ``` -我们得到了与第二条管道相同的结果! +我们得到了与第二条 pipeline 相同的结果! ```python out [{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, @@ -470,4 +470,4 @@ print(results) {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -这些偏移量非常有用的另一个任务示例是问答。深入研究这个管道,我们将在下一节中进行,也将使我们能够了解 🤗 Transformers 库中标记器的最后一个功能:当我们将输入截断为给定长度时处理溢出的标记。 +另一个非常需要偏移量的任务领域是问答。我们将在下一部分深入探索这个 pipeline。同时我们也会看到🤗 Transformers 库中 tokenizer 的最后一个特性:在我们将输入超过给定长度时,处理溢出的 tokens。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index 1c23ad523..b2b31eaf3 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# QA 管道中的快速标记器 [[QA 管道中的快速标记器]] +# 在 QA 管道中使用快速 tokenizer [[在 QA 管道中使用快速 tokenizer ]] {#if fw === 'pt'} @@ -22,7 +22,7 @@ {/if} -我们现在将深入研究 **question-answering** 管道,看看如何利用偏移量从上下文中获取手头问题的答案,有点像我们在上一节中对分组实体所做的。然后我们将看到我们如何处理最终被截断的非常长的上下文。如果您对问答任务不感兴趣,可以跳过此部分。 +我们现在将深入研究 `question-answering` 管道,看看如何利用偏移量从上下文(context)中获取当前问题的答案,这与我们在上一节中处理分组实体的方式有些相似。我们会看到如何处理那些因为过长而最终被截断的上下文(context)。如果你对问答任务不感兴趣,可以跳过这一节。 {#if fw === 'pt'} @@ -36,7 +36,7 @@ ## 使用 `question-answering` 管道 [[使用 `question-answering` 管道]] -正如我们在[Chapter 1](/course/chapter1),我们可以使用 **question-answering** 像这样的管道以获得问题的答案: +正如我们在 [第一章](/course/chapter1) ,我们可以使用 `question-answering` 像这样的管道以获得问题的答案: ```py from transformers import pipeline @@ -57,7 +57,8 @@ question_answerer(question=question, context=context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -与其他管道不同,它不能截断和拆分长于模型接受的最大长度的文本(因此可能会丢失文档末尾的信息),此管道可以处理非常长的上下文,并将返回回答这个问题,即使它在最后: +与其他不能处理超过模型接受的最大长度的文本的管道不同,这个管道可以处理非常长的上下文(context),并且即使答案在末尾也能返回问题的答案: + ```py long_context = """ @@ -107,11 +108,11 @@ question_answerer(question=question, context=long_context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -让我们看看它是如何做到这一切的! +让我们看看它是如何做到这一点的! ## 使用模型进行问答 [[使用模型进行问答]] -与任何其他管道一样,我们首先对输入进行标记化,然后通过模型将其发送。默认情况下用于的检查点 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)(名称中的“squad”来自模型微调的数据集;我们将在[Chapter 7](/course/chapter7/7)): +与任何其他管道一样,我们首先对输入进行 tokenize,然后将其传入模型。 `question-answering` 管道默认情况下用于的 checkpoint 是 [distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad) (名字中的"squad"源自模型微调所用的数据集;我们将在 [第七章](/course/chapter7/7) 详细讨论 SQuAD 数据集): {#if fw === 'pt'} @@ -141,14 +142,14 @@ outputs = model(**inputs) {/if} -请注意,我们将问题和上下文标记为一对,首先是问题 +请注意在这里,我们将问题放在前面和上下文放后面,一起作为一对进行tokenization。
An example of tokenization of question and context
-问答模型的工作方式与我们迄今为止看到的模型略有不同。以上图为例,该模型已经过训练,可以预测答案开始的标记的索引(此处为 21)和答案结束处的标记的索引(此处为 24)。这就是为什么这些模型不返回一个 logits 的张量,而是返回两个:一个用于对应于答案的开始标记的 logits,另一个用于对应于答案的结束标记的 logits。由于在这种情况下我们只有一个包含 66 个标记的输入,我们得到: +问答模型的工作方式与我们迄今为止看到的模型略有不同。以上图为例,模型训练的目标是来预测答案开始的 token 的索引(这里是 21)和答案结束的 token 的索引(这里是 24)。这就是为什么这些模型不返回一个 logits 的张量,而是返回两个:一个对应于答案的开始 token 的 logits,另一个对应于答案的结束 token 的 logits。在这个例子中,我们的输入包含了 66 个 token ,因此我们得到: ```py start_logits = outputs.start_logits @@ -170,9 +171,9 @@ torch.Size([1, 66]) torch.Size([1, 66]) {/if} -为了将这些 logits 转换为概率,我们将应用一个 softmax 函数——但在此之前,我们需要确保我们屏蔽了不属于上下文的索引。我们的输入是 **[CLS] question [SEP] context [SEP]** ,所以我们需要屏蔽问题的标记以及 **[SEP]** 令牌。我们将保留 **[CLS]** 然而,因为某些模型使用它来表示答案不在上下文中。 +为了将这些 logits 转换为概率,我们将使用一个 softmax 函数——但在此之前,我们需要确保我们屏蔽了不属于上下文的索引。我们的输入格式是 `[CLS] question [SEP] context [SEP]` ,所以我们需要屏蔽 question 的 tokens 以及 `[SEP]` token 。不过,我们将保留 `[CLS]` ,因为某些模型使用它来表示答案不在上下文中。 -由于我们将在之后应用 softmax,我们只需要用一个大的负数替换我们想要屏蔽的 logits。在这里,我们使用 **-10000** : +由于我们将在之后使用 softmax,我们只需要将我们想要屏蔽的 logits 替换为一个大的负数就可以在计算 softmax 的时候屏蔽他们。在这里,我们使用 `-10000` : {#if fw === 'pt'} @@ -180,9 +181,9 @@ torch.Size([1, 66]) torch.Size([1, 66]) import torch sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 不屏蔽 [CLS] token mask[0] = False mask = torch.tensor(mask)[None] @@ -196,9 +197,9 @@ end_logits[mask] = -10000 import tensorflow as tf sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 不屏蔽 [CLS] token mask[0] = False mask = tf.constant(mask)[None] @@ -208,7 +209,7 @@ end_logits = tf.where(mask, -10000, end_logits) {/if} -现在我们已经正确屏蔽了与我们不想预测的位置相对应的 logits,我们可以应用 softmax: +现在我们已经屏蔽了与我们不想预测的位置相对应的 logits,接下来我们可以使用 softmax: {#if fw === 'pt'} @@ -226,22 +227,24 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() {/if} -在这个阶段,我们可以采用开始和结束概率的 argmax——但我们最终可能会得到一个大于结束索引的开始索引,所以我们需要采取更多的预防措施。我们将计算每个可能的概率 **start_index** 和 **end_index** 在哪里 **start_index <= end_index** ,然后取元组 **(start_index, end_index)** 以最高的概率。 +在这个阶段,我们可以取开始和结束概率的 argmax —— 但是我们可能会得到一个比结束索引大的开始索引,因此我们需要采取一些更多的措施来处理这些特殊情况。我们将在满足 `start_index <= end_index` 的前提下计算每个可能的 `start_index` 和 `end_index` 的概率,然后取概率最高的 `(start_index, end_index)` 元组。 + +假设事件"答案开始于 `start_index` "和"答案结束于 `end_index` "是独立的,答案在 `start_index` 开始并在 `end_index` 结束的概率是: -假设事件“答案开始于 **start_index** ”和“答案结束于 **end_index** ” 要独立,答案开始于的概率 **start_index** 并结束于 **end_index** 是: +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times\mathrm{end\_probabilities}[\mathrm{end\_index}]$$ -$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ +所以,要计算所有的分数,我们只需要计算所有的 `start_index <= end_index` 的 \($$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times\mathrm{end\_probabilities}[\mathrm{end\_index}]$$\) 的乘积。 -所以,要计算所有的分数,我们只需要计算所有的产品 \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) where `start_index <= end_index`. -首先让我们计算所有可能的产品: +首先让我们计算所有可能的乘积: + ```py scores = start_probabilities[:, None] * end_probabilities[None, :] ``` {#if fw === 'pt'} -然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: +然后我们将 `start_index > end_index` 的值设置为 `0` 来屏蔽他们(其他概率都是正数)。 `torch.triu()` 函数返回传入的 2D 张量的上三角部分,所以我们可以使用它来完成屏蔽: ```py import numpy as np @@ -250,15 +253,15 @@ scores = torch.triu(scores) ``` {:else} -然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: +然后我们将 `start_index > end_index` 的值设置为 `0` 来屏蔽他们(其他概率都是正数)。 `np.triu()` 函数返回传入的 2D 张量的上三角部分,所以我们做可以使用它来完成屏蔽: ```py scores = np.triu(scores) ``` {/if} -现在我们只需要得到最大值的索引。由于 PyTorch 将返回展平张量中的索引,因此我们需要使用地板除法 **//** 和模数 **%** 操作以获得 **start_index** 和 **end_index** : +现在我们只需要得到最大值的索引。由于 PyTorch 将返回展平(flattened)后张量中的索引,因此我们需要使用向下取整的除法 `//` 和取模 `%` 操作来获得 `start_index` 和 `end_index` : ```py max_index = scores.argmax().item() @@ -267,7 +270,7 @@ end_index = max_index % scores.shape[1] print(scores[start_index, end_index]) ``` -我们还没有完全完成,但至少我们已经有了正确的答案分数(您可以通过将其与上一节中的第一个结果进行比较来检查这一点): +我们还没有完全完成,但至少我们已经有了正确的答案分数(你可以通过将其与上一节中的第一个结果进行比较来检查这一点): ```python out 0.97773 @@ -275,11 +278,11 @@ print(scores[start_index, end_index]) -✏️ **试试看!** 计算五个最可能的答案的开始和结束索引。 +✏️ **试试看!** 计算五个最可能的答案的开始和结束索引。 -我们有 **start_index** 和 **end_index** 就标记而言的答案,所以现在我们只需要转换为上下文中的字符索引。这是偏移量非常有用的地方。我们可以像在令牌分类任务中一样抓住它们并使用它们: +我们有了答案的 `start_index` 和 `end_index` ,所以现在我们只需要将他们转换为上下文中的字符索引。这就是偏移量将会非常有用的地方。我们可以像我们在 token 分类任务中那样获取偏移量并使用它们: ```py inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) @@ -290,7 +293,8 @@ _, end_char = offsets[end_index] answer = context[start_char:end_char] ``` -现在我们只需要格式化所有内容以获得我们的结果: +现在我们只需要格式化所有内容,获取我们的结果: + ```py result = { @@ -309,17 +313,17 @@ print(result) 'score': 0.97773} ``` -太棒了!这和我们的第一个例子一样! +太棒了!这和我们上面获取的结果一样! -✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案。要检查您的结果,请返回到第一个管道并在调用它时传入。 +✏️ **试试看!** 使用你之前计算的最佳分数来显示五个最可能的答案。你可以回到之前的 QA pipeline,并在调用时传入 `top_k=5` 来对比检查你的结果。 -## 处理长上下文 [[处理长上下文]] +## 处理长文本 [[处理长文本]] -如果我们尝试对我们之前作为示例使用的问题和长上下文进行标记化,我们将获得比在 **question-answering** 管道(即 384): +如果我们尝试将我们之前使用的长问题和长上下文进行 tokenize,我们将得到一个比 `question-answering` pipeline 中使用的最大长度(384)更大的 tokens 数量: ```py inputs = tokenizer(question, long_context) @@ -330,7 +334,7 @@ print(len(inputs["input_ids"])) 461 ``` -因此,我们需要在最大长度处截断我们的输入。有几种方法可以做到这一点,但我们不想截断问题,只想截断上下文。由于上下文是第二个句子,我们将使用 **"only_second"** 截断策略。那么出现的问题是问题的答案可能不在截断上下文中。例如,在这里,我们选择了一个答案在上下文末尾的问题,当我们截断它时,答案不存在 +所以,我们需要将我们的输入截断到模型允许输入的最大长度。我们可以用几种方式做到这一点,但我们不想截断问题部分,只想截断上下文部分,并且由于上下文部分是第二项,因此我们将使用 `"only_second"` 截断策略。然后又出现了新的问题:问题的答案可能在截断后被丢弃了,并没有在截断后保留下来的上下文文本中。例如,我们选了一个问题,其中的答案在上下文的末尾,当我们截断它时,答案就不在里面了: ```py inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") @@ -373,9 +377,9 @@ Why should I use transformers? """ ``` -这意味着模型将很难选择正确的答案。为了解决这个问题, **question-answering** 管道允许我们将上下文分成更小的块,指定最大长度。为确保我们不会在完全错误的位置拆分上下文以找到答案,它还包括块之间的一些重叠。 +这意味着模型将很难找到正确的答案。为了解决这个问题, `question-answering` 管道允许我们将上下文分成更小的块,指定最大长度。为了确保我们不在刚好可能找到答案的地方将上下文分割,它还在各块之间包含了一些重叠。 -我们可以让分词器(快或慢)通过添加来为我们做这件事 **return_overflowing_tokens=True** ,我们可以指定我们想要的重叠 **stride** 争论。这是一个使用较小句子的示例: +我们可以通过添加 `return_overflowing_tokens=True` 参数,并可以用 `stride` 参数指定我们想要的重叠长度来让 tokenizer (快速或慢速)为我们做这个工作。下面是一个使用较短的句子的例子: ```py sentence = "This sentence is not too long but we are going to split it anyway." @@ -397,9 +401,9 @@ for ids in inputs["input_ids"]: '[CLS] it anyway. [SEP]' ``` -正如我们所看到的,句子已被分成多个块,使得每个条目 **inputs["input_ids"]** 最多有 6 个标记(我们需要添加填充以使最后一个条目与其他条目的大小相同)并且每个条目之间有 2 个标记的重叠。 +正如我们所看到的,句子已被分成多个块,使得每个条目 `inputs["input_ids"]` 最多有 6 个 token (我们需要添加填充以使分割后的最后一个条目与其他条目的大小相同)并且每个条目之间有 2 个 token 的重叠。 -让我们仔细看看标记化的结果: +让我们仔细看看tokenization的结果: ```py print(inputs.keys()) @@ -409,7 +413,8 @@ print(inputs.keys()) dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) ``` -正如预期的那样,我们得到了输入 ID 和一个注意力掩码。最后一个键, **overflow_to_sample_mapping** , 是一个映射,它告诉我们每个结果对应哪个句子——这里我们有 7 个结果,它们都来自我们通过标记器的(唯一)句子: + +正如我们所预期的,我们得到了inputs ID 和注意力掩码。最后一个键,overflow_to_sample_mapping,是一个映射,告诉我们每个结果对应哪个句子——在这里,我们有 7 个结果,它们都来自我们传递给 tokenizer 的(唯一的)句子: ```py print(inputs["overflow_to_sample_mapping"]) @@ -419,7 +424,7 @@ print(inputs["overflow_to_sample_mapping"]) [0, 0, 0, 0, 0, 0, 0] ``` -当我们将几个句子标记在一起时,这更有用。例如,这个: +当我们一起对多个句子 tokenize 时,这会更有用。例如,这个: ```py sentences = [ @@ -433,16 +438,16 @@ inputs = tokenizer( print(inputs["overflow_to_sample_mapping"]) ``` -让我们: +输出的结果是: ```python out [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] ``` -这意味着第一个句子像以前一样分成 7 个块,接下来的 4 个块来自第二个句子。 +这意味着第一句话被分割成 7 个块,就像上面的例子一样,后面的 4 个块来自第二句话。 +现在让我们回到我们的长上下文。默认情况下, `question-answering pipeline` 使用我们之前提到的 384 作为最大长度,步长为 128,这与模型的微调方式相对应(你可以通过在调用 pipeline 时传递 `max_seq_len` 和 `stride` 参数来调整这些参数)。因此,我们在 tokenization 时将使用微调时使用的这些参数。我们还会添加填充(使样本具有相同的长度,这样我们就可以构建拼接成一个矩形的张量),并获取偏移量: -现在让我们回到我们的长期背景。默认情况下 **question-answering** 管道使用的最大长度为 384,正如我们之前提到的,步长为 128,这对应于模型微调的方式(您可以通过传递 **max_seq_len** 和 **stride** 调用管道时的参数)。因此,我们将在标记化时使用这些参数。我们还将添加填充(具有相同长度的样本,因此我们可以构建张量)以及请求偏移量: ```py inputs = tokenizer( @@ -457,7 +462,8 @@ inputs = tokenizer( ) ``` -那些 **inputs** 将包含模型期望的输入 ID 和注意力掩码,以及偏移量和 **overflow_to_sample_mapping** 我们刚刚谈到。由于这两个不是模型使用的参数,我们将把它们从 **inputs** (我们不会存储地图,因为它在这里没有用)在将其转换为张量之前: +这些 `inputs` 将包含模型期望的inputs ID 和注意力掩码,以及我们刚刚谈到的偏移量和 `overflow_to_sample_mapping` 。由于模型不需要这两个参数,我们将它们从 `inputs` 中删除(我们不会存储映射的字典,因为这在这里没有用)然后将 `inputs` 转换为张量: + {#if fw === 'pt'} @@ -489,7 +495,7 @@ print(inputs["input_ids"].shape) {/if} -我们的长上下文被分成两部分,这意味着在它通过我们的模型后,我们将有两组开始和结束 logits: +我们的长上下文被分成两部分,这意味着在经过我们的模型后,我们将得到两组开始和结束的 logits: ```py outputs = model(**inputs) @@ -513,17 +519,17 @@ torch.Size([2, 384]) torch.Size([2, 384]) {/if} -和以前一样,我们在采用 softmax 之前首先屏蔽不属于上下文的标记。我们还屏蔽了所有填充标记(由注意掩码标记): +和以前一样,我们在计算 softmax 之前屏蔽不属于上下文的 token 。我们还屏蔽了所有填充 token (如注意力掩码): {#if fw === 'pt'} ```py sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context tokens 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 取消对 [CLS] token 的屏蔽 mask[0] = False -# Mask all the [PAD] tokens +# 屏蔽所有的 [PAD] tokens mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) start_logits[mask] = -10000 @@ -534,11 +540,11 @@ end_logits[mask] = -10000 ```py sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context tokens 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 取消对 [CLS] token的屏蔽 mask[0] = False -# Mask all the [PAD] tokens +# 屏蔽所有的 [PAD] tokens mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) start_logits = tf.where(mask, -10000, start_logits) @@ -565,7 +571,7 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() {/if} -下一步与我们对小上下文所做的类似,但我们对两个块中的每一个都重复它。我们将分数归因于所有可能的答案跨度,然后取得分最高的跨度: +下一步与我们对短的上下文所做的类似,但在这里我们将对两个块分别进行处理。我们为所有可能的回答范围赋予一个得分,然后选择得分最高的范围: {#if fw === 'pt'} @@ -605,15 +611,16 @@ print(candidates) [(0, 18, 0.33867), (173, 184, 0.97149)] ``` -这两个候选对应于模型能够在每个块中找到的最佳答案。该模型对正确答案在第二部分更有信心(这是一个好兆头!)。现在我们只需要将这两个标记跨度映射到上下文中的字符跨度(我们只需要映射第二个标记以获得我们的答案,但看看模型在第一个块中选择了什么很有趣)。 +这两个候选范围对应的是模型在每个块中能够找到的最好的答案。模型对于正确的答案在第二部分更有信心(这是个好兆头!)。现在我们只需要将这两个 token 范围映射到上下文中的字符范围(我们只需要映射第二个就能得到我们的答案,但是看看模型在第一块中选取了什么作为答案还是很有意思的)。 + -✏️ **试试看!** 修改上面的代码以返回五个最可能的答案的分数和跨度(总计,而不是每个块)。 +✏️ **试试看!** 调整上面的代码,以返回五个最可能的答案的得分和范围(对于整个上下文,而不是单个块)。 -这 **offsets** 我们之前抓取的实际上是一个偏移量列表,每个文本块有一个列表: +我们之前抓取 `offsets` 的实际上是一个偏移量列表,每个文本块都有一个列表: ```py for candidate, offset in zip(candidates, offsets): @@ -630,12 +637,12 @@ for candidate, offset in zip(candidates, offsets): {'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} ``` -如果我们忽略第一个结果,我们会得到与这个长上下文的管道相同的结果——是的! +如果我们选择分数最高的第二个结果,我们会得到与 QA 管道相同结果——耶! -✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案(对于整个上下文,而不是每个块)。要检查您的结果,请返回到第一个管道并在调用它时传入。 +✏️ **试试看!** 使用你之前计算的最佳分数来显示五个最可能的答案(对于整个上下文,而不是单个块)。如果想要与 pipeline 对比检查你的结果的话,返回之前的 QA 管道,并在调用时传入 `top_k=5` 的参数。 -我们对分词器功能的深入研究到此结束。我们将在下一章再次将所有这些付诸实践,届时我们将向您展示如何在一系列常见的 NLP 任务上微调模型。 +我们已经结束了我们对 tokenizer 能力的深入探究。在下一章,我们将展示如何在一系列常见的 NLP 任务上微调模型,我们将对这些内容再次付诸实践。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index 137aba74c..6064c0179 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -1,4 +1,4 @@ -# 标准化和预标记化 [[标准化和预标记化]] +# 标准化和预分词 [[标准化和预分词]] -在我们更深入地研究与 Transformer 模型(字节对编码 [BPE]、WordPiece 和 Unigram)一起使用的三种最常见的子词标记化算法之前,我们将首先看一下每个标记器应用于文本的预处理。以下是标记化管道中步骤的高级概述: +在深入探讨 Transformer 模型常用的三种分词算法(字节对编码[BPE]、WordPiece 和 Unigram)之前,我们首先来看看 tokenizer 对文本进行了哪些预处理。以下是 tokenization 过程的高度概括:
The tokenization pipeline.
-在将文本拆分为子标记之前(根据其模型),分词器执行两个步骤: _normalization_ 和 _pre-tokenization_. +在分词(根据其模型)之前,tokenizer 需要进行两个步骤: `标准化(normalization)` 和 `预分词(pre-tokenization)` 。 -## 正常化 [[正常化]] +## `标准化(normalization)` [[`标准化(normalization)`]] -标准化步骤涉及一些常规清理,例如删除不必要的空格、小写和/或删除重音符号。如果你熟悉[Unicode normalization](http://www.unicode.org/reports/tr15/)(例如 NFC 或 NFKC),这也是 tokenizer 可能应用的东西。 +标准化步骤涉及一些常规清理,例如删除不必要的空格、小写和“/”或删除重音符号。如果你熟悉 [Unicode 标准化](http://www.unicode.org/reports/tr15/) (例如 NFC 或 NFKC),那么这也是 tokenizer 可能会使用的东西。 -🤗Transformers **tokenizer** 有一个属性叫做 **backend_tokenizer** 它提供了对 🤗 Tokenizers 库中底层标记器的访问: +🤗 Transformers 的 `tokenizer` 具有一个名为 `backend_tokenizer` 的属性,该属性可以访问来自🤗 Tokenizers 库的底层 tokenizer 。 ```py from transformers import AutoTokenizer @@ -35,7 +35,7 @@ print(type(tokenizer.backend_tokenizer)) ``` -**normalizer** 的属性 **tokenizer** 对象有一个 **normalize_str()** 我们可以用来查看标准化是如何执行的方法: +`tokenizer` 对象的 `normalizer` 属性具有一个 `normalize_str()` 方法,我们可以使用该方法查看如何进行标准化: ```py print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) @@ -45,22 +45,21 @@ print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü? 'hello how are u?' ``` -在这个例子中,因为我们选择了 **bert-base-uncased** 检查点,标准化应用小写并删除重音。 +在这个例子中,由于我们选择了 `bert-base-uncased` checkpoint,所以会在标准化的过程中转换为小写并删除重音。 -✏️ **试试看!** 从检查点加载标记器并将相同的示例传递给它。您可以看到分词器的带壳和无壳版本之间的主要区别是什么? - +✏️ **试试看!** 从 `bert-base-cased` checkpoint 加载 tokenizer 并处理相同的示例。看一看 tokenizer 的 `cased` 和 `uncased` 版本之间的主要区别是什么? -## 预标记化 [[预标记化]] +## 预分词 [[预分词]] -正如我们将在下一节中看到的,分词器不能单独在原始文本上进行训练。相反,我们首先需要将文本拆分为小实体,例如单词。这就是预标记化步骤的用武之地。 正如我们在[Chapter 2](/course/chapter2), 基于单词的标记器可以简单地将原始文本拆分为空白和标点符号的单词。这些词将是分词器在训练期间可以学习的子标记的边界。 +正如我们将在下一节中看到的,tokenizer 一般不会在原始文本上进行训练。因此,我们首先需要将文本拆分为更小的实体,例如单词。这就是预分词步骤的作用。正如我们在 [第二章](/course/chapter2) 中看到的,基于单词的 tokenizer 可以简单地根据空格和标点符号将原始文本拆分为单词。这些词将是 tokenizer 在训练期间可以学习的子词的边界。 -要查看快速分词器如何执行预分词,我们可以使用 **pre_tokenize_str()** 的方法 **pre_tokenizer** 的属性 **tokenizer** 目的: +要查看快速 tokenizer 如何执行预分词,我们可以使用 `tokenizer` 对象的 `pre_tokenizer` 属性的 `pre_tokenize_str()` 方法: ```py tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") @@ -70,25 +69,26 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] ``` -请注意分词器如何已经跟踪偏移量,这就是它如何为我们提供上一节中使用的偏移量映射。这里分词器忽略了这两个空格,只用一个替换它们,但偏移量在 **are** 和 **you** 考虑到这一点。 +请注意 tokenizer 记录了偏移量,这是就是我们在前一节中使用的偏移映射。在这里 tokenizer 将两个空格并将它们替换为一个,从 `are` 和 `you` 之间的偏移量跳跃可以看出来这一点。 -由于我们使用的是 BERT 分词器,预分词涉及对空格和标点符号进行拆分。对于这一步,其他标记器可以有不同的规则。例如,如果我们使用 GPT-2 标记器: +由于我们使用的是 BERT tokenizer 所以预分词会涉及到在空白和标点上进行分割。其他的 tokenizer 可能会对这一步有不同的规则。例如,如果我们使用 GPT-2 的 tokenizer ```py tokenizer = AutoTokenizer.from_pretrained("gpt2") tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") ``` -它也会在空格和标点符号上拆分,但它会保留空格并将它们替换为 **Ġ** 符号,如果我们解码令牌,则使其能够恢复原始空格: +它也会在空格和标点符号上拆分,但它会保留空格并将它们替换为 `Ġ` 符号,如果我们对 tokens 进行解码,则使其能够恢复原始空格: + ```python out [('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), ('?', (19, 20))] ``` -另请注意,与 BERT 分词器不同,此分词器不会忽略双空格 +也请注意,与 BERT tokenizer 不同的是,这个 tokenizer 不会忽略双空格。 -最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 分词器: +最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 tokenizer ```py tokenizer = AutoTokenizer.from_pretrained("t5-small") @@ -99,26 +99,25 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] ``` -与 GPT-2 标记器一样,这个标记器保留空格并用特定标记替换它们( **_** ),但 T5 分词器只在空格上拆分,而不是标点符号。还要注意,它默认在句子的开头添加了一个空格(之前 **Hello** ) 并忽略了之间的双空格 **are** 和 **you** . +与 GPT-2 的 tokenizer 类似,这个 tokenizer 保留空格并用特定 token 替换它们( `_` ),但 T5 tokenizer 只在空格上拆分,不考虑标点符号。另外,它会在句子开头(在 `Hello` 之前)默认添加一个空格,并忽略 `are` 和 `you` 之间的双空格。 -现在我们已经了解了一些不同的标记器如何处理文本,我们可以开始探索底层算法本身。我们首先快速浏览一下广泛适用的 SentencePiece;然后,在接下来的三个部分中,我们将研究用于子词标记化的三种主要算法是如何工作的。 +现在我们对一些不同的 tokenizer 如何处理文本有了一些了解,接下来我们就可以开始探索底层算法本身。我们将先简要了解一下广泛适用的 SentencePiece;然后,在接下来的三节中,我们将研究用于子词分词的三种主要算法的工作原理。 -## 句子 [[句子]] +## SentencePiece [[SentencePiece]] -[SentencePiece](https://github.com/google/sentencepiece) 是一种用于文本预处理的标记化算法,您可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符替换空格, **▁** .与 Unigram 算法结合使用(参见[section 7](/course/chapter7/7)), 它甚至不需要预标记化步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 + [SentencePiece](https://github.com/google/sentencepiece) 是一种用于文本预处理的 tokenization 算法,你可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符 `▁` 替换空格。与 Unigram 算法结合使用(参见 [第七节](/course/chapter7/7) )时,它甚至不需要预分词步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 -SentencePiece 的另一个主要特点是可逆标记化:由于没有对空格进行特殊处理,因此只需通过将它们连接起来并替换 **_** s 带空格——这会导致标准化的文本。正如我们之前看到的,BERT 分词器删除了重复的空格,因此它的分词是不可逆的。 +SentencePiece 的另一个主要特点是可逆 tokenization:由于没有对空格进行特殊处理,解码 tokens 的时候只需将它们连接起来,然后将 `_` 替换为空格,就可以还原原始的文本。如我们之前所见,BERT tokenizer 会删除重复的空格,所以它的分词不是可逆的。 ## 算法概述 [[算法概述]] -在下面的部分中,我们将深入研究三种主要的子词标记化算法:BPE(由 GPT-2 和其他人使用)、WordPiece(例如由 BERT 使用)和 Unigram(由 T5 和其他人使用)。在我们开始之前,这里是它们各自工作原理的快速概述。如果您还没有理解,请在阅读下一节后立即回到此表。 - +在下面的部分中,我们将深入研究三种主要的子词 tokenization 算法:BPE(由 GPT-2 等使用)、WordPiece(由 BERT 使用)和 Unigram(由 T5 等使用)。在我们开始之前,先来快速了解它们各自的工作方式。如果你还不能理解,不妨在阅读接下来的每一节之后返回此表格进行查看。 -Model | BPE | WordPiece | Unigram +模型 | BPE | WordPiece | Unigram :----:|:---:|:---------:|:------: -Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens -Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus -Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token -Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training +训练 | 从小型词汇表开始,学习合并 token 的规则 | 从小型词汇表开始,学习合并 token 的规则 | 从大型词汇表开始,学习删除 token 的规则 +训练步骤 | 合并对应最常见的 token 对 | 合并对应得分最高的 token 对,优先考虑每个独立 token 出现频率较低的对 | 删除会在整个语料库上最小化损失的词汇表中的所有 token +学习 | 合并规则和词汇表 | 仅词汇表 | 含有每个 token 分数的词汇表 +编码 | 将一个单词分割成字符并使用在训练过程中学到的合并 | 从开始处找到词汇表中的最长子词,然后对其余部分做同样的事 | 使用在训练中学到找到最可能的 token 分割方式 现在让我们深入了解 BPE! \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index e923db49b..4a8e1d4f7 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -1,4 +1,4 @@ -# 字节对编码标记化 [[字节对编码标记化]] +# BPE tokenization 算法 [[BPE tokenization算法]] -字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于标记化。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 +字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于 tokenization。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 -💡 本节深入介绍了BPE,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 +💡 本节深入介绍了 BPE,甚至展示了一个完整的实现。如果你只想大致了解 tokenization 算法,可以跳到最后。 -## 训练算法 [[训练算法]] +## BPE 训练 [[BPE 训练]] -BPE 训练首先计算语料库中使用的唯一单词集(在完成标准化和预标记化步骤之后),然后通过获取用于编写这些单词的所有符号来构建词汇表。一个非常简单的例子,假设我们的语料库使用了这五个词: +BPE 训练首先计算语料库中使用的唯一单词集合(在完成标准化和预分词步骤之后),然后取出用来编写这些词的所有符号来构建词汇表。举一个非常简单的例子,假设我们的语料库使用了这五个词: ``` "hug", "pug", "pun", "bun", "hugs" ``` -基础词汇将是 `["b", "g", "h", "n", "p", "s", "u"]`。对于实际情况,基本词汇表将包含所有 ASCII 字符,至少,可能还包含一些 Unicode 字符。如果您正在标记的示例使用不在训练语料库中的字符,则该字符将转换为未知标记。这就是为什么许多 NLP 模型在分析带有表情符号的内容方面非常糟糕的原因之一。 +基础单词集合将是 `["b", "g", "h", "n", "p", "s", "u"]` 。在实际应用中,基本词汇表将至少包含所有 ASCII 字符,可能还包含一些 Unicode 字符。如果你正在 tokenization 不在训练语料库中的字符,则该字符将转换为未知 tokens,这就是为什么许多 NLP 模型在分析带有表情符号的内容的结果非常糟糕的原因之一。 -TGPT-2 和 RoBERTa 标记器(非常相似)有一个聪明的方法来处理这个问题: 他们不把单词看成是用 Unicode 字符写的,而是用字节写的。这样,基本词汇表的大小很小(256),但你能想到的每个字符仍将被包含在内,而不会最终转换为未知标记。这个技巧被称为 *字节级 BPE*。 +GPT-2 和 RoBERTa (这两者非常相似)的 tokenizer 有一个巧妙的方法来处理这个问题:他们不把单词看成是用 Unicode 字符编写的,而是用字节编写的。这样,基本词汇表的大小很小(256),但是能包含几乎所有你能想象的字符,而不会最终转换为未知 tokens 这个技巧被称为 `字节级(byte-level) BPE` 。 -获得这个基本词汇后,我们添加新的标记,直到通过学习*合并*达到所需的词汇量,这是将现有词汇表的两个元素合并为一个新元素的规则。因此,在开始时,这些合并将创建具有两个字符的标记,然后,随着训练的进行,会创建更长的子词。 +获得这个基础单词集合后,我们通过学习 `合并(merges)` 来添加新的 tokens 直到达到期望的词汇表大小。合并是将现有词汇表中的两个元素合并为一个新元素的规则。所以,一开始会创建出含有两个字符的 tokens 然后,随着训练的进展,会产生更长的子词。 -在分词器训练期间的任何一步,BPE 算法都会搜索最常见的现有标记对 ("对",这里我们指的是单词中的两个连续标记)。最频繁的一对将被合并,我们冲洗并重复下一步。 +在分词器训练期间的任何一步,BPE 算法都会搜索最常见的现有 tokens 对 (在这里,“对”是指一个词中的两个连续 tokens )。最常见的这一对会被合并,然后我们重复这个过程。 -回到我们之前的例子,让我们假设单词具有以下频率: +回到我们之前的例子,让我们假设单词具有以下频率: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -意味着 `"hug"` 在语料库中出现了10次, `"pug"` 5次, `"pun"` 12次, `"bun"` 4次, 以及 `"hugs"` 5次。我们通过将每个单词拆分为字符(形成我们初始词汇表的字符)来开始训练,这样我们就可以将每个单词视为一个标记列表: +意思是 `"hug"` 在语料库中出现了 10 次, `"pug"` 出现了 5 次, `"pun"` 出现了 12 次, `"bun"` 出现了 4 次, `"hugs"` 出现了 5 次。我们通过将每个单词拆分为字符(形成我们初始词汇表的字符)来开始训练,这样我们就可以将每个单词视为一个 tokens 列表: ``` ("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) ``` -然后我们看成对。这对 `("h", "u")` 出现在单词 `"hug"` 和 `"hugs"`中,所以语料库中总共有15次。不过,这并不是最频繁的一对:这个荣誉属于 `("u", "g")`,它出现在 `"hug"`, `"pug"`, 以及 `"hugs"`中,在词汇表中总共 20 次。 +然后我们看看相邻的字符对。 `("h", "u")` 在词 `"hug"` 和 `"hugs"` 中出现,所以在语料库中总共出现了 15 次。然而,最常见的对属于 `("u", "g")` ,它在 `"hug"` 、 `"pug"` 和 `"hugs"` 中出现,总共在词汇表中出现了 20 次。 -因此,标记器学习的第一个合并规则是 `("u", "g") -> "ug"`,意思就是 `"ug"` 将被添加到词汇表中,并且这对应该合并到语料库的所有单词中。在这个阶段结束时,词汇表和语料库看起来像这样: +因此,tokenizer 学习的第一个合并规则是 `("u", "g") -> "ug"` ,意思就是 `"ug"` 将被添加到词汇表中,且应在语料库的所有词中合并这一对。在这个阶段结束时,词汇表和语料库看起来像这样: ``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] -Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug"] +语料库: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) ``` -现在我们有一些导致标记长于两个字符的对: 例如 `("h", "ug")`, 在语料库中出现15次。然而,这个阶段最频繁的对是 `("u", "n")`,在语料库中出现16次,所以学到的第二个合并规则是 `("u", "n") -> "un"`。将其添加到词汇表并合并所有现有的这个对,将出现: +现在我们有一些对,继续合并的话会产生一个比两个字符长的 tokens 例如 `("h", "ug")` ,在语料库中出现 15 次。然而,这个阶段出现频率最高的对是 `("u", "n")` ,在语料库中出现 16 次,所以学到的第二个合并规则是 `("u", "n") -> "un"` 。将其添加到词汇表并合并所有现有的这个对,将出现: ``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] -Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +语料库: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) ``` -现在最频繁的一对是 `("h", "ug")`,所以我们学习了合并规则 `("h", "ug") -> "hug"`,这给了我们第一个三个字母的标记。合并后,语料库如下所示: +现在最频繁的一对是 `("h", "ug")` ,所以我们学习了合并规则 `("h", "ug") -> "hug"` ,这形成了我们第一个三个字母的 tokens 合并后,语料库如下所示: ``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] -Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +语料库: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) ``` -我们继续这样合并,直到达到我们所需的词汇量。 +我们继续这样合并,直到达到我们所需的词汇量。 -✏️ **现在轮到你了!**你认为下一个合并规则是什么? +✏️ **现在轮到你了!** 你认为下一个合并规则是什么? -## 标记化算法 [[标记化算法]] +## tokenization [[tokenization]] -标记化紧跟训练过程,从某种意义上说,通过应用以下步骤对新输入进行标记: +完成训练之后就可以对新的输入 tokenization 了,从某种意义上说,新的输入会依照以下步骤对新输入进行 tokenization: -1. 规范化 -2. 预标记化 +1. 标准化 +2. 预分词 3. 将单词拆分为单个字符 -4. 将学习的合并规则按顺序应用于这些拆分 +4. 根据学习的合并规则,按顺序合并拆分的字符 -让我们以我们在训练期间使用的示例为例,学习三个合并规则: +让我们以我们在训练期间使用的示例为例,Tokenizer 学习到了三个合并规则: ``` ("u", "g") -> "ug" @@ -97,19 +97,19 @@ Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5 ("h", "ug") -> "hug" ``` -这个单词 `"bug"` 将被标记为 `["b", "ug"]`。然而 `"mug"`,将被标记为 `["[UNK]", "ug"]`,因为字母 `"m"` 不再基本词汇表中。同样,单词`"thug"` 会被标记为 `["[UNK]", "hug"]`: 字母 `"t"` 不在基本词汇表中,应用合并规则首先导致 `"u"` 和 `"g"` 被合并,然后是 `"hu"` 和 `"g"` 被合并。 +在这种情况下,单词 `"bug"` 将被转化为 `["b", "ug"]` 。然而 `"mug"` ,将被转换为 `["[UNK]", "ug"]` ,因为字母 `"m"` 不再基本词汇表中。同样,单词 `"thug"` 会被转换为 `["[UNK]", "hug"]` :字母 `"t"` 不在基本词汇表中,使用合并规则首先会将 `"u"` 和 `"g"` 合并,然后将 `"h"` 和 `"ug"` 合并。 -✏️ **现在轮到你了!** 你认为这个词 `"unhug"` 将如何被标记? +✏️ **现在轮到你了!** 你认为这个词 `"unhug"` 将如何被 tokenization? -## 实现 BPE [[实现 BPE]] +## 实现 BPE 算法[[实现 BPE 算法]] -现在让我们看一下 BPE 算法的实现。这不会是你可以在大型语料库上实际使用的优化版本;我们只是想向你展示代码,以便你可以更好地理解算法 +现在,让我们看一下 BPE 算法的实现。这并不是在大型语料库上实际使用的经过优化的版本;我们只是想向你展示代码,以便你可以更好地理解算法 -首先我们需要一个语料库,所以让我们用几句话创建一个简单的语料库: +首先,我们需要一个语料库,让我们创建一个含有几句话的简单语料库: ```python corpus = [ @@ -120,7 +120,7 @@ corpus = [ ] ``` -接下来,我们需要将该语料库预先标记为单词。由于我们正在复制 BPE 标记器(如 GPT-2),我们将使用 `gpt2` 标记器作为预标记化的标记器: +接下来,我们需要将该语料库预分词为单词。由于我们正在复现一个 BPE tokenizer (例如 GPT-2),我们将使用 `gpt2` 分词器进行预分词: ```python from transformers import AutoTokenizer @@ -128,7 +128,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -然后我们在进行预标记化时计算语料库中每个单词的频率: +然后,我们在进行预分词的同时计算语料库中每个单词的频率: ```python from collections import defaultdict @@ -151,7 +151,7 @@ defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) ``` -下一步是计算基本词汇,由语料库中使用的所有字符组成: +下一步是计算基础词汇表,这由语料库中使用的所有字符组成: ```python alphabet = [] @@ -170,19 +170,19 @@ print(alphabet) 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] ``` -我们还在该词汇表的开头添加了模型使用的特殊标记。对于GPT-2,唯一的特殊标记是 `"<|endoftext|>"`: +我们还在该词汇表的开头添加了模型使用的特殊 tokens 对于 GPT-2,唯一的特殊 tokens 是 `"<|endoftext|>"` : ```python vocab = ["<|endoftext|>"] + alphabet.copy() ``` -我们现在需要将每个单词拆分为单独的字符,以便能够开始训练: +我们现在需要将每个单词拆分为单独的字符,以便能够开始训练: ```python splits = {word: [c for c in word] for word in word_freqs.keys()} ``` -现在我们已准备好进行训练,让我们编写一个函数来计算每对的频率。我们需要在训练的每个步骤中使用它: +现在我们已准备好进行训练,让我们编写一个函数来计算每对字符的频率。我们需要在训练的每个步骤中使用它: ```python def compute_pair_freqs(splits): @@ -197,7 +197,7 @@ def compute_pair_freqs(splits): return pair_freqs ``` -让我们来看看这个字典在初始拆分后的一部分: +让我们来看看这个字典在初始合并后的一些结果: ```python pair_freqs = compute_pair_freqs(splits) @@ -217,7 +217,8 @@ for i, key in enumerate(pair_freqs.keys()): ('t', 'h'): 3 ``` -现在, 找到最频繁的对只需要一个快速的循环: +现在,只需要一个简单的循环就可以找到出现频率最高的对: + ```python best_pair = "" @@ -235,14 +236,14 @@ print(best_pair, max_freq) ('Ġ', 't') 7 ``` -所以第一个要学习的合并是 `('Ġ', 't') -> 'Ġt'`, 我们添加 `'Ġt'` 到词汇表: +所以第一个要学习的合并规则是 `('Ġ', 't') -> 'Ġt'` ,我们将 `'Ġt'` 添加到词汇表: ```python merges = {("Ġ", "t"): "Ġt"} vocab.append("Ġt") ``` -要继续接下来的步骤,我们需要在我们的`分词`字典中应用该合并。让我们为此编写另一个函数: +接下来,我们需要在我们的 `splits` 字典中进行这个合并。让我们为此编写另一个函数: ```python def merge_pair(a, b, splits): @@ -261,7 +262,7 @@ def merge_pair(a, b, splits): return splits ``` -我们可以看看第一次合并的结果: +我们可以观察一下第一次合并的结果: ```py splits = merge_pair("Ġ", "t", splits) @@ -272,7 +273,7 @@ print(splits["Ġtrained"]) ['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] ``` -现在我们有了循环所需的一切,直到我们学会了我们想要的所有合并。我们的目标是词汇量达到50: +现在我们有了我们需要的所有代码,可以循环直到我们学习到我们想要的所有合并。让我们把目标词汇表的大小设定为 50: ```python vocab_size = 50 @@ -290,7 +291,7 @@ while len(vocab) < vocab_size: vocab.append(best_pair[0] + best_pair[1]) ``` -结果,我们学习了 19 条合并规则(初始词汇表的大小 31 -- 30 字母字符,加上特殊标记): +最终,我们学习了 19 条合并规则(初始词汇量为 31 —— 字母表中的 30 个字符,加上特殊 token ): ```py print(merges) @@ -303,7 +304,7 @@ print(merges) ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} ``` -词汇表由特殊标记、初始字母和所有合并结果组成: +词汇表由特殊 token 初始字母和所有合并结果组成: ```py print(vocab) @@ -317,11 +318,11 @@ print(vocab) -💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为当有最频繁对的选择时,我们选择遇到的第一个, 而 🤗 Tokenizers 库根据内部ID选择第一个。 +💡 在同一语料库上使用 `train_new_from_iterator()` 可能不会产生完全相同的词汇表。这是因为当有多个出现频率最高的对时,我们选择遇到的第一个,而 🤗 Tokenizers 库根据内部 ID 选择第一个。 -为了对新文本进行分词,我们对其进行预分词、拆分,然后应用学到的所有合并规则: +为了对新文本进行分词,我们对其进行预分词、拆分,然后使用学到的所有合并规则: ```python def tokenize(text): @@ -341,7 +342,8 @@ def tokenize(text): return sum(splits, []) ``` -我们可以在任何由字母表中的字符组成的文本上尝试这个: +我们可以尝试在任何由字母表中的字符组成的文本上进行此操作: + ```py tokenize("This is not a token.") @@ -353,8 +355,8 @@ tokenize("This is not a token.") -⚠️ 如果存在未知字符,我们的实现将抛出错误,因为我们没有做任何处理它们。GPT-2 实际上没有未知标记(使用字节级 BPE 时不可能得到未知字符),但这可能发生在这里,因为我们没有在初始词汇表中包含所有可能的字节。 BPE 的这方面超出了本节的范围,因此我们忽略了细节。 +⚠️ 如果存在未知字符,我们的实现将抛出错误,因为我们没有做任何处理它们。GPT-2 实际上没有未知 tokens (使用字节级 BPE 时不可能得到未知字符),但这里的代码可能会出现这个错误,因为我们并未在初始词汇中包含所有可能的字节。BPE 的这一部分已超出了本节的范围,因此我们省略了一些细节。 -这就是 BPE 算法!接下来,我们将看看 WordPiece。 \ No newline at end of file +至此,BPE 算法的介绍就到此结束!接下来,我们将研究 WordPiece 算法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index 8f3b746ba..799c8737c 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -1,4 +1,4 @@ -# WordPiece 标记化 [[WordPiece 标记化]] +# WordPiece tokenization 算法 [[WordPiece tokenization算法]] -WordPiece 是 Google 为预训练 BERT 而开发的标记化算法。此后,它在不少基于 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常相似,但实际标记化的方式不同。 +WordPiece 是 Google 开发的用于 BERT 预训练的分词算法。自此之后,很多基于 BERT 的 Transformer 模型都复用了这种方法,比如 DistilBERT,MobileBERT,Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常类似,但实际的分词方法有所不同。 -💡 本节深入介绍 WordPiece,甚至展示完整的实现。如果您只想大致了解标记化算法,可以跳到最后。 +💡 本节详细讲述了 WordPiece,甚至展示了一个完整的实现。如果你只想对这个分词算法有个大概的理解,可以直接跳到最后。 -## 训练算法 [[训练算法]] +## WordPiece 训练 [[WordPiece 训练]] -⚠️ Google 从未开源 WordPiece 训练算法的实现,因此以下是我们基于已发表文献的最佳猜测。它可能不是 100% 准确的。 +⚠️ Google 从未开源 WordPiece 训练算法的实现,因此以下是我们基于已发表文献的最佳猜测。它可能并非 100% 准确的。 -与 BPE 一样,WordPiece 从一个小词汇表开始,包括模型使用的特殊标记和初始字母表。因为它通过添加前缀来识别子词 (如同 `##` 对于 BERT),每个单词最初是通过将该前缀添加到单词内的所有字符来拆分的。所以,例如 `"word"` ,像这样拆分: +与BPE 一样,WordPiece 也是从包含模型使用的特殊 tokens 和初始字母表的小词汇表开始的。由于它是通过添加前缀(如 BERT 中的 `##` )来识别子词的,每个词最初都会通过在词内部所有字符前添加该前缀进行分割。因此,例如 `"word"` 将被这样分割: ``` w ##o ##r ##d ``` -因此,初始字母表包含出现在单词开头的所有字符以及出现在单词内部的以 WordPiece 前缀开头的字符。 +因此,初始字母表包含所有出现在单词第一个位置的字符,以及出现在单词内部并带有 WordPiece 前缀的字符。 -然后,再次像 BPE 一样,WordPiece 学习合并规则。主要区别在于选择要合并的对的方式。WordPiece 不是选择最频繁的对,而是使用以下公式计算每对的分数: +然后,同样像 BPE 一样,WordPiece 会学习合并规则。主要的不同之处在于合并对的选择方式。WordPiece 不是选择频率最高的对,而是对每对计算一个得分,使用以下公式: -$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ -通过将配对的频率除以其每个部分的频率的乘积, 该算法优先合并单个部分在词汇表中频率较低的对。例如,它不一定会合并 `("un", "##able")` 即使这对在词汇表中出现的频率很高,因为 `"un"` 和 `"##able"` 很可能每个词都出现在很多其他词中并且出现频率很高。相比之下,像 `("hu", "##gging")` 可能会更快地合并 (假设 "hugging" 经常出现在词汇表中),因为 `"hu"` 和 `"##gging"` 这两个词单独出现地频率可能较低。 +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times\mathrm{freq\_of\_second\_element})$$ -让我们看看我们在 BPE 训练示例中使用的相同词汇: +通过将两部分合在一起的频率除以其中各部分的频率的乘积,该算法优先合并那些在词汇表中单独出现出现的对。例如,即使 `("un", "##able")` 这对在词汇表中出现的频率很高,它也不一定会被合并,因为 `"un"` 和 `"##able"` 这两对可能会在很多其他词中出现,频率很高。相比之下,像 `("hu", "##gging")` 这样的对可能会更快地被合并(假设单词“hugging”在词汇表中出现的频率很高),因为 `"hu"` 和 `"##gging"` 可能分别出现的频率较低。 + +我们使用与 BPE 示例相同的词汇表: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -这里的拆分将是: +经过分割之后将会是: ``` ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) ``` -所以最初的词汇将是 `["b", "h", "p", "##g", "##n", "##s", "##u"]` (如果我们暂时忘记特殊标记)。最频繁的一对是 `("##u", "##g")` (目前20次),但 `"##u"` 单独出现的频率非常高,所以它的分数不是最高的(它是 1 / 36)。所有带有 `"##u"` 的对实际上都有相同的分数(1 / 36),所以分数最高的对是 `("##g", "##s")` -- 唯一没有 `"##u"` 的对-- 1 / 20,所以学习的第一个合并是 `("##g", "##s") -> ("##gs")`。 +所以最初的词汇表将会是 `["b", "h", "p", "##g", "##n", "##s", "##u"]` (如果我们暂时忽略特殊 tokens )。出现频率最高的一对是 `("##u", "##g")` (目前 20 次),但 `"##u"` 和其他单词一起出现的频率非常高,所以它的分数不是最高的(分数是 1 / 36)。所有带有 `"##u"` 的对实际上都有相同的分数(1 / 36),所以分数最高的对是 `("##g", "##s")` —— 唯一没有 `"##u"` 的对——分数是 1 / 20,所以学习的第一个合并是 `("##g", "##s") -> ("##gs")` 。 -请注意,当我们合并时,我们删除了两个标记之间的 `##`,所以我们添加 `"##gs"` 到词汇表中,并在语料库的单词中应用该合并: +请注意,当我们合并时,我们会删除两个 tokens 之间的 `##` ,所以我们将 `"##gs"` 添加到词汇表中,并将语料库的单词按照改规则进行合并: ``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] -Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +语料库: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) ``` -在这一点中, `"##u"` 是在所有可能的对中,因此它们最终都具有相同的分数。假设在这种情况下,第一对被合并, `("h", "##u") -> "hu"`。这使得我们: +此时, `"##u"` 出现在所有可能的对中,因此它们最终都具有相同的分数。在这种情况下,第一个对会被合并,于是我们得到了 `("h", "##u") -> "hu"` 规则: ``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] -Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +语料库: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) ``` -然后下一个最高的分数由 `("hu", "##g")` 和 `("hu", "##gs")` 共享(1/15,与其他所有对的 1/21 相比),因此合并得分最高的第一对: +然后,下一个最佳得分的对是 `("hu", "##g")` 和 `("hu", "##gs")` (得分为 1/15,而所有其他配对的得分为 1/21),因此得分最高的第一对合并: ``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] -Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +语料库: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) ``` -我们继续这样处理,直到达到我们所需的词汇量。 +然后我们就按此方式继续,直到我们达到所需的词汇表大小。 -✏️ **现在轮到你了!** 下一个合并规则是什么? +✏️ **现在轮到你了!** 下一个合并规则是什么? + -## 标记化算法 [[标记化算法]] +## tokenization 算法 [[tokenization 算法]] -WordPiece 和 BPE 中的标记化的不同在于 WordPiece 只保存最终词汇,而不是学习的合并规则。从要标记的单词开始,WordPiece 找到词汇表中最长的子词,然后对其进行拆分。例如,如果我们使用上面例子中学到的词汇,对于单词 `"hugs"`,词汇表中从头开始的最长子词是 `"hug"`,所以我们在那里拆分并得到 `["hug", "##s"]`。 然后我们继续使用词汇表中的 `"##s"`,因此 `"hugs"` 的标记化是 `["hug", "##s"]`. +WordPiece 和 BPE 的分词方式有所不同,WordPiece 只保存最终词汇表,而不保存学习到的合并规则。WordPiece 从待分词的词开始,找到词汇表中最长的子词,然后在其处分割。例如,如果我们使用上述示例中学习到的词汇表,对于词 `"hugs"` ,从开始处的最长子词在词汇表中是 `"hug"` ,所以我们在那里分割,得到 `["hug", "##s"]` 。然后我们继续处理 `"##s"` ,它在词汇表中,所以 `"hugs"` 的分词结果是 `["hug", "##s"]` 。 -使用 BPE, 我们将按顺序应用学习到的合并并将其标记为 `["hu", "##gs"]`,所以编码不同。 +如果使用 BPE,我们会按照学习到的合并规则进行合并,并将其分词为 `["hu", "##gs"]` ,不同的字词分词算法所以最终得到的编码是不同的。 -再举一个例子,让我们看看 `"bugs"` 将如何被标记化。 `"b"` 是从词汇表中单词开头开始的最长子词,所以我们在那里拆分并得到 `["b", "##ugs"]`。然后 `"##u"` 是词汇表中从 `"##ugs"` 开始的最长的子词,所以我们在那里拆分并得到 `["b", "##u, "##gs"]`。最后, `"##gs"` 在词汇表中,所以最后一个列表是 `"bugs"` 的标记化。 +再举一个例子,让我们看看 `"bugs"` 将如何分词的。 `"b"` 是从词汇表中单词开头开始的最长子词,所以我们在那里分割并得到 `["b", "##ugs"]` 。然后 `"##u"` 是词汇表中从 `"##ugs"` 开始的最长的子词,所以我们在那里拆分并得到 `["b", "##u, "##gs"]` 。最后, `"##gs"` 在词汇表中,因此 `"bugs"` 的分词结果是: `["b", "##u, "##gs"]` 。 -当分词达到无法在词汇表中找到子词的阶段时, 整个词被标记为未知 -- 例如, `"mug"` 将被标记为 `["[UNK]"]`,就像 `"bum"` (即使我们可以以 `"b"` 和 `"##u"` 开始, `"##m"` 不在词汇表中,由此产生的标记将只是 `["[UNK]"]`, 不是 `["b", "##u", "[UNK]"]`)。这是与 BPE 的另一个区别,BPE 只会将不在词汇表中的单个字符分类为未知。 +当分词过程中无法在词汇库中找到该子词时,整个词会被标记为 unknown(未知)—— 例如, `"mug"` 将被标记为 `["[UNK]"]` , `"bum"` 也是如此(即使我们的词汇表中包含 `"b"` 和 `"##u"` 开始,但是 `"##m"` 不在词汇表中,因此最终的分词结果只会是 `["[UNK]"]` ,而不是 `["b", "##u", "[UNK]"]` )。这是与 BPE 的另一个区别,BPE 只会将不在词汇库中的单个字符标记为 unknown。 -✏️ **现在轮到你了!** `"pugs"` 将被如何标记? +✏️ **现在轮到你了!** `"pugs"` 将被如何分词? ## 实现 WordPiece [[实现 WordPiece]] -现在让我们看一下 WordPiece 算法的实现。与 BPE 一样,这只是教学,你将无法在大型语料库中使用它。 +现在让我们看一下 WordPiece 算法的实现。与 BPE 一样,这只是教学示例,你不能在大型语料库上使用。 -我们将使用与 BPE 示例中相同的语料库: +我们将使用与 BPE 示例中相同的语料库: ```python corpus = [ @@ -112,7 +114,7 @@ corpus = [ ] ``` -首先,我们需要将语料库预先标记为单词。由于我们正在复制 WordPiece 标记器 (如 BERT),因此我们将使用 `bert-base-cased` 标记器用于预标记化: +首先,我们需要将语料库预分词为单词。由于我们正在复刻 WordPiece tokenizer (如 BERT),因此我们将使用 `bert-base-cased` tokenizer 进行预分词: ```python from transformers import AutoTokenizer @@ -120,7 +122,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") ``` -然后我们在进行预标记化时计算语料库中每个单词的频率: +然后我们在进行预分词的同时,计算语料库中每个单词的频率: ```python from collections import defaultdict @@ -143,7 +145,7 @@ defaultdict( 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) ``` -正如我们之前看到的,字母表是由单词的所有第一个字母组成的唯一集合,以及出现在前缀为 `##` 的其他字母: +如我们之前看到的,字母表是一个独特的集合,由所有单词的第一个字母以及所有以 `##` 为前缀和在单词中的其他字母组成: ```python alphabet = [] @@ -166,13 +168,13 @@ print(alphabet) 'w', 'y'] ``` -我们还在该词汇表的开头添加了模型使用的特殊标记。在使用 BERT 的情况下,它是列表 `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: +我们还在该词汇表的开头添加了模型使用的特殊 tokens,在使用 BERT 的情况下,特殊 tokens 是 `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : ```python vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() ``` -接下来我们需要拆分每个单词, 所有不是第一个字母的字母都以 `##` 为前缀: +接下来我们需要将每个单词进行分割,除了第一个字母外,其他字母都需要以 `##` 为前缀: ```python splits = { @@ -181,7 +183,7 @@ splits = { } ``` -现在我们已经准备好训练了,让我们编写一个函数来计算每对的分数。我们需要在训练的每个步骤中使用它: +现在我们已经准备好训练了,让我们编写一个函数来计算每对的分数。我们需要在训练的每个步骤中使用它: ```python def compute_pair_scores(splits): @@ -205,7 +207,7 @@ def compute_pair_scores(splits): return scores ``` -让我们来看看这个字典在初始拆分后的一部分: +让我们来看看在初始分割后的部分字典: ```python pair_scores = compute_pair_scores(splits) @@ -224,7 +226,7 @@ for i, key in enumerate(pair_scores.keys()): ('##h', '##e'): 0.011904761904761904 ``` -现在,找到得分最高的对只需要一个快速循环: +现在,只需要一个快速循环就可以找到得分最高的对: ```python best_pair = "" @@ -241,13 +243,13 @@ print(best_pair, max_score) ('a', '##b') 0.2 ``` -所以第一个要学习的合并是 `('a', '##b') -> 'ab'`, 并且我们添加 `'ab'` 到词汇表中: +所以第一个要学习的合并是 `('a', '##b') -> 'ab'` ,并且我们添加 `'ab'` 到词汇表中: ```python vocab.append("ab") ``` -要继续接下来的步骤,我们需要在我们的 `拆分` 字典中应用该合并。让我们为此编写另一个函数: +接下来,我们需要对 `splits` 字典进行这种合并。让我们为此写另一个函数: ```python def merge_pair(a, b, splits): @@ -266,7 +268,7 @@ def merge_pair(a, b, splits): return splits ``` -我们可以看看第一次合并的结果: +我们可以看看第一次合并的结果: ```py splits = merge_pair("a", "##b", splits) @@ -277,7 +279,7 @@ splits["about"] ['ab', '##o', '##u', '##t'] ``` -现在我们有了循环所需的一切,直到我们学会了我们想要的所有合并。我们的目标词汇量为70: +现在我们有了合并循环的所有代码。让我们设定词汇表的大小为 70: ```python vocab_size = 70 @@ -297,7 +299,7 @@ while len(vocab) < vocab_size: vocab.append(new_token) ``` -然后我们可以查看生成的词汇表: +然后我们可以查看生成的词汇表: ```py print(vocab) @@ -311,15 +313,15 @@ print(vocab) '##ut'] ``` -正如我们所看到的,与 BPE 相比,这个标记器将单词的一部分作为标记学习得更快一些。 +如我们所见,相较于 BPE(字节对编码),此分词器在学习单词部分作为 tokens 时稍快一些。 -💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为 🤗 Tokenizers 库没有为训练实现 WordPiece(因为我们不完全确定它的内部结构),而是使用 BPE。 +💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为 🤗 Tokenizers 库没有为训练实现 WordPiece(因为我们不完全确定它的真实实现方式),而是使用了 BPE。 -为了对新文本进行分词,我们对其进行预分词、拆分,然后对每个单词应用分词算法。也就是说,我们从第一个词的开头寻找最大的子词并将其拆分,然后我们在第二部分重复这个过程,对于该词的其余部分和文本中的以下词,依此类推: +要对新文本进行分词,我们先预分词,再进行分割,然后在每个词上使用分词算法。也就是说,我们寻找从第一个词开始的最大子词并将其分割,然后我们对第二部分重复此过程,以此类推,对该词以及文本中的后续词进行分割: ```python def encode_word(word): @@ -337,7 +339,7 @@ def encode_word(word): return tokens ``` -让我们用词汇表中的一个单词和另一个不在词汇表中的单词进行测试: +让我们使用词汇表中的一个词和一个不在词汇表中的词上测试一下: ```python print(encode_word("Hugging")) @@ -349,7 +351,7 @@ print(encode_word("HOgging")) ['[UNK]'] ``` -现在,让我们编写一个标记文本的函数: +现在,让我们编写一个对文本分词的函数: ```python def tokenize(text): @@ -359,7 +361,7 @@ def tokenize(text): return sum(encoded_words, []) ``` -我们可以在任何文本上尝试: +我们可以在任何文本上尝试: ```python tokenize("This is the Hugging Face course!") @@ -370,4 +372,4 @@ tokenize("This is the Hugging Face course!") '##e', '[UNK]'] ``` -这就是 WordPiece 算法的全部内容!现在让我们来看看 Unigram。 +这就是 WordPiece 算法的全部内容!现在让我们来看看 Unigram。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index 95202c1e3..cbe85e855 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -1,4 +1,4 @@ -# Unigram标记化 [[Unigram标记化]] +# Unigram tokenization 算法 [[Unigram tokenization算法]] -在 SentencePiece 中经常使用 Unigram 算法,该算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的标记化算法。 +Unigram 算法常用于 SentencePiece 中,该切分算法被 AlBERT,T5,mBART,Big Bird 和 XLNet 等模型广泛采用。 -💡 本节深入介绍了 Unigram,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 +💡 本节将深入探讨 Unigram,甚至展示完整的实现过程。如果你只想大致了解 tokenization 算法,可以直接跳到章节末尾。 -## 训练算法 [[训练算法]] +## Unigram 训练 [[Unigram 训练]] -与 BPE 和 WordPiece 相比,Unigram 在另一个方向上工作:它从一个较大的词汇表开始,然后从中删除标记,直到达到所需的词汇表大小。有多种选项可用于构建基本词汇表:例如,我们可以采用预标记化单词中最常见的子串,或者在具有大词汇量的初始语料库上应用 BPE。 +与BPE 和 WordPiece 相比,Unigram 的工作方式正好相反:它从一个大词汇库开始,然后逐步删除词汇,直到达到目标词汇库大小。构建基础词汇库有多种方法:例如,我们可以选取预切分词汇中最常见的子串,或者在具有大词汇量的初始语料库上进行 BPE 得到一个初始词库。 -在训练的每一步,Unigram 算法都会在给定当前词汇的情况下计算语料库的损失。然后,对于词汇表中的每个符号,算法计算如果删除该符号,整体损失会增加多少,并寻找增加最少的符号。这些符号对语料库的整体损失影响较小,因此从某种意义上说,它们“不太需要”并且是移除的最佳候选者。 +在训练的每一步,Unigram 算法都会在给定当前词汇的情况下计算语料库的损失。然后,对于词汇表中的每个符号,算法计算如果删除该符号,整体损失会增加多少,并寻找删除后损失增加最少的符号。这些符号对语料库的整体损失影响较小,因此从某种意义上说,它们“相对不必要”并且是移除的最佳候选者。 -这是一个非常昂贵的操作,所以我们不只是删除与最低损失增加相关的单个符号,而且\\(p\\) (\\(p\\)是一个可以控制的超参数,通常是 10 或 20)与最低损失增加相关的符号的百分比。然后重复这个过程,直到词汇量达到所需的大小。 +这个过程非常消耗计算资源,因此我们不只是删除与最低损失增长相关的单个符号,而是删除与最低损失增长相关的百分之/p (p 是一个可以控制的超参数,通常是 10 或 20)的符号。然后重复此过程,直到词汇库达到所需大小。 -请注意,我们从不删除基本字符,以确保可以标记任何单词。 +注意,我们永远不会删除基础的单个字符,以确保任何词都能被切分。 -现在,这仍然有点模糊:算法的主要部分是计算语料库的损失,并查看当我们从词汇表中删除一些标记时它会如何变化,但我们还没有解释如何做到这一点。这一步依赖于 Unigram 模型的标记化算法,因此我们接下来将深入研究。 +然而,这仍然有些模糊:算法的主要部分是在词汇库中计算语料库的损失并观察当我们从词汇库中移除一些符号时损失如何变化,但我们尚未解释如何做到这一点。这一步依赖于 Unigram 模型的切分算法,接下来我们将深入研究。 -我们将重用前面示例中的语料库: +我们将复用前面例子中的语料库: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -对于此示例,我们将采用初始词汇表的所有严格子字符串: +而在这个例子中,我们将取这个语料库中所有的子串作为初始词汇库: ``` ["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] ``` -## 标记化算法 [[标记化算法]] +## tokenization 算法 [[tokenization算法]] -Unigram 模型是一种语言模型,它认为每个标记都独立于它之前的标记。它是最简单的语言模型,从某种意义上说, 给定先前上下文的标记 X 的概率就是标记 X 的概率。因此,如果我们使用 Unigram 语言模型生成文本,我们将始终预测最常见的标记。 +Unigram 模型是一种语言模型,它认为每个符号都与其之前的符号独立。这是最简单的语言模型,因此给定之前的上下文情况下,符号 X 的概率就是符号 X 的概率。所以,如果我们使用 Unigram 语言模型来生成文本,我们的预测总会输出最常见的符号。 -给定标记的概率是它在原始语料库中的频率(我们找到它的次数),除以词汇表中所有标记的所有频率的总和(以确保概率总和为 1)。例如, `"ug"` 在 `"hug"` 、 `"pug"` 以及 `"hugs"` 中,所以它在我们的语料库中的频率为 20。 +给定符号的概率是其在原始语料库中的频率(我们计算它出现的次数),除以词汇库中所有符号的频率总和(以确保概率总和为 1)。例如, `"ug"` 出现在 `"hug"` , `"pug"` 和 `"hugs"` 中,所以它在我们的语料库中的频率是 20。 -以下是词汇表中所有可能的子词的出现频率: +以下是词汇库中所有可能出现子词的频率: ``` ("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) ("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) ``` -所以,所有频率之和为210, 并且子词 `"ug"` 出现的概率是 20/210。 +所以,所有频率之和为 210,子词 `"ug"` 出现的概率是 20/210。 -✏️ **现在轮到你了!** 编写代码来计算上面的频率,并仔细检查显示的结果以及总和是否正确。 +✏️ **现在轮到你了!** 编写代码计算上述频率,然后验证结果的准确性,以及概率的总和是否正确。 -现在,为了对给定的单词进行标记,我们将所有可能的分割视为标记,并根据 Unigram 模型计算每个分割的概率。由于所有标记都被认为是独立的,所以这个概率只是每个标记概率的乘积。例如, `"pug"` 的标记化 `["p", "u", "g"]` 的概率为: +现在,为了对一个给定的单词进行分词,我们会查看所有可能的分词组合,并根据 Unigram 模型计算出每种可能的概率。由于所有的分词都被视为独立的,因此这个单词分词的概率就是每个子词概率的乘积。例如,将 `"pug"` 分词为 `["p", "u", "g"]` 的概率为: + $$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ -相比之下,标记化 `["pu", "g"]` 的概率为: +相比之下,将 “pug” 分词为 `["pu", "g"]` 的概率为: $$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ -所以一个更有可能。一般来说,具有尽可能少的标记的标记化将具有最高的概率(因为每个标记重复除以 210),这对应于我们直观想要的:将一个单词分成尽可能少的标记。 +因此,后者的可能性更大。一般来说,分词数最少的分词方式将具有最高的概率(因为每个分词都要除以 210),这正符合我们的直觉:将一个词分割为尽可能少的子词。 -使用 Unigram 模型对单词进行分词是概率最高的分词。在示例 `"pug"` 中,这里是我们为每个可能的分割获得的概率: +利用 Unigram 模型对一个词进行分词,就是找出概率最高的分词方式。以 `"pug"` 为例,我们得到的各种可能分词方式的概率如下: ``` ["p", "u", "g"] : 0.000389 @@ -80,13 +81,13 @@ $$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{21 ["pu", "g"] : 0.0022676 ``` -所以, `"pug"` 将被标记为 `["p", "ug"]` 或者 `["pu", "g"]`, 取决于首先遇到这些分割中的哪一个(请注意,在更大的语料库中,这样的相等的情况很少见)。 +因此, `"pug"` 将被分词为 `["p", "ug"]` 或 `["pu", "g"]` ,取决于哪种分词方式排在前面(注意,在更大的语料库中,像这样的相等情况将很少见)。 -在这种情况下,很容易找到所有可能的分割并计算它们的概率,但一般来说会有点困难。有一种用于此的经典算法,称为 *维特比(Viterbi)算法*。本质上,我们可以构建一个图来检测给定单词的可能分割,如果从_a_到_b_的子词在词汇表中,则从字符_a_到字符_b_之间存在一个分支,并将子词的概率归因于该分支。 +在这个例子中,找出所有可能的分词方式并计算其概率是容易的,但在语料库比较大的情况下有些困难。有一个经典的算法可以用来计算这个概率,叫做 `Viterbi 算法` 。事实上,我们可以通过创建一个图来表示一个给定单词的所有可能分词,如果从字符 `a` 到字符 `b` 的子词在词汇表中,那么就存在一个从 `a` 到 `b` 的分支,分支的边就是进行这个切分的概率。 -为了在该图中找到将具有最佳分数的路径,维特比算法为单词中的每个位置确定在该位置结束的具有最佳分数的分段。由于我们从开始到结束,可以通过循环遍历以当前位置结尾的所有子词,然后使用该子词开始位置的最佳标记化分数来找到最佳分数。然后,我们只需要展开到达终点所采取的路径。 +为了在图中找到得分最高的路径,Viterbi 算法会确定出每个位置上结束的最佳得分分割。我们从头到尾进行处理,可以通过遍历所有在当前位置结束的子词,然后使用这个子词开始位置的最佳得分,找到最高得分。然后,我们只需要回溯走过的路径,就能找到最终的最优路径。 -让我们看一个使用我们的词汇表和单词 `"unhug"` 的例子。对于每个位置,以最好的分数结尾的子词如下: +让我们看一个使用我们的词汇表和单词 `"unhug"` 的例子。对于每个位置,最佳切分子词的分数如下: ``` Character 0 (u): "u" (score 0.171429) @@ -96,27 +97,28 @@ Character 3 (u): "un" "hu" (score 0.005442) Character 4 (g): "un" "hug" (score 0.005442) ``` -因此 `"unhug"` 将被标记为 `["un", "hug"]`。 +因此 “unhug” 将被分词为 `["un", "hug"]` 。 + -✏️ **现在轮到你了!** 确定单词 `"huggun"` 的标记化及其分数。 +✏️ **现在轮到你了!** 确定单词 “huggun” 的分词方式以及其得分。 ## 回到训练 [[回到训练]] -现在我们已经了解了标记化的工作原理,我们可以更深入地研究训练期间使用的损失。在任何给定的阶段,这个损失是通过对语料库中的每个单词进行标记来计算的,使用当前词汇表和由语料库中每个标记的频率确定的 Unigram 模型(如前所述)。 +我们已经了解了如何进行分词处理,接下来我们可以更详细地了解一下在训练过程中如何计算损失值。在训练的每个阶段,我们都会将语料库中的每个词进行分词,分词所使用的词表和 Unigram 模型是基于目前的情况(即根据每个词在语料库中出现的频率)来确定的。然后,基于这种分词结果,我们就可以计算出损失值(loss)。 -语料库中的每个词都有一个分数,损失是这些分数的负对数似然 -- 即所有词的语料库中所有词的总和 `-log(P(word))`。 +语料库中的每个词都有一个分数,损失(loss)值是这些分数的负对数似然 —— 即所有词的语料库中所有词的 `-log(P(word))` 总和 -让我们用以下语料库回到我们的例子: +让我们回到我们的例子,以下是我们的语料库: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -每个单词的标记化及其各自的分数是: +每个单词的分词及其相应的得分如下: ``` "hug": ["hug"] (score 0.071428) @@ -126,34 +128,34 @@ Character 4 (g): "un" "hug" (score 0.005442) "hugs": ["hug", "s"] (score 0.001701) ``` -所以损失是: +因此,损失值(loss)是: ``` 10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 ``` -现在我们需要计算删除每个标记如何影响损失。这相当乏味,所以我们在这里只对两个标记进行操作,并保存整个过程以备有代码来帮助我们。在这个(非常)特殊的情况下,我们对所有单词有两个等效的标记:正如我们之前看到的,例如, `"pug"` 可以以相同的分数被标记为 `["p", "ug"]`。因此,去除词汇表中的 `"pu"` 标记将给出完全相同的损失。 +现在,我们需要计算移除每个 token 对损失值的影响。这个过程颇为繁琐,所以我们这里仅对两个单词进行演示,在我们编写代码来协助处理这个过程时,再对全部的词进行 tokenization 的处理。在这个(非常)特殊的例子中,我们对单词的两种等效的分词方式:例如,“pug”可以被分词为 `["pu", "g"]` ,也可以被分词为 `["p", "ug"]` ,获得的分数是相同的。因此,去除词汇表中的 `"pu"` 损失值还会是一样的。 -另一方面,去除 `"hug"` 损失变得更糟, 因为 `"hug"` 和 `"hugs"` 的标记化会变成: +但是,去除 `"hug"` 之后,损失会变得更糟,因为 `"hug"` 和 `"hugs"` 的 tokenization 会变成: ``` "hug": ["hu", "g"] (score 0.006802) "hugs": ["hu", "gs"] (score 0.001701) ``` -这些变化将导致损失增加: +这些变化将导致损失增加: ``` - 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 ``` -因此, 标记 `"pu"`可能会从词汇表中删除,但不会删除 `"hug"`. +因此, `"pu"` tokens 可能会从词汇表中移除,但 `"hug"` 则不会。 ## 实现 Unigram [[实现 Unigram]] -现在让我们在代码中实现我们迄今为止看到的所有内容。与 BPE 和 WordPiece 一样,这不是 Unigram 算法的有效实现(恰恰相反),但它应该可以帮助你更好地理解它。 +现在让我们在代码中实现上面看到的所内容。与 BPE 和 WordPiece 一样,这不是 Unigram 算法的高效实现(恰恰相反,这套代码的效率非常低),但它应该可以帮助你更好地理解它。 -我们将使用与之前相同的语料库作为示例: +我们将使用与之前相同的语料库作为示例: ```python corpus = [ @@ -164,7 +166,7 @@ corpus = [ ] ``` -这一次,我们将使用 `xlnet-base-cased` 作为我们的模型: +这次,我们将使用 `xlnet-base-cased` 作为我们的模型: ```python from transformers import AutoTokenizer @@ -172,7 +174,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") ``` -与 BPE 和 WordPiece 一样,我们首先计算语料库中每个单词的出现次数: +与 BPE 和 WordPiece 一样,我们首先计算语料库中每个单词的出现次数: ```python from collections import defaultdict @@ -187,7 +189,7 @@ for text in corpus: word_freqs ``` -然后,我们需要将我们的词汇表初始化为大于我们最终想要的词汇量。我们必须包含所有基本字符(否则我们将无法标记每个单词),但对于较大的子字符串,我们将只保留最常见的字符,因此我们按频率对它们进行排序: +然后,我们需要将我们的词汇表初始化为大于我们最终想要的词汇量。我们必须包含所有基本的单个字符(否则我们将无法对每个单词赋予一个 token ),但对于较大的子字符串,我们将只保留最常见的字符,因此我们按出现频率对它们进行排序: ```python char_freqs = defaultdict(int) @@ -195,11 +197,11 @@ subwords_freqs = defaultdict(int) for word, freq in word_freqs.items(): for i in range(len(word)): char_freqs[word[i]] += freq - # Loop through the subwords of length at least 2 + # 循环遍历长度至少为2的子字 for j in range(i + 2, len(word) + 1): subwords_freqs[word[i:j]] += freq -# Sort subwords by frequency +# 按频率对子词排序 sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) sorted_subwords[:10] ``` @@ -208,7 +210,7 @@ sorted_subwords[:10] [('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] ``` -我们用最优的子词对字符进行分组,以获得大小为 300 的初始词汇表: +我们用最优的子词对字符进行分组,以获得大小为 300 的初始词汇表: ```python token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] @@ -217,11 +219,11 @@ token_freqs = {token: freq for token, freq in token_freqs} -💡 SentencePiece 使用一种称为增强后缀数组(ESA)的更高效算法来创建初始词汇表。 +💡 SentencePiece 使用一种名为增强后缀数组(ESA)的更高效的算法来创建初始词汇表。 -接下来,我们计算所有频率的总和,将频率转换为概率。对于我们的模型,我们将存储概率的对数,因为添加对数比乘以小数在数值上更稳定,这将简化模型损失的计算: +接下来,我们需要计算所有频率的总和,将频率转化为概率。在我们的模型中,我们将存储概率的对数,因为相较于小数相乘,对数相加在数值上更稳定,而且这将简化模型损失的计算: ```python from math import log @@ -230,11 +232,11 @@ total_sum = sum([freq for token, freq in token_freqs.items()]) model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -N现在主要功能是使用 Viterbi 算法标记单词的功能。正如我们之前看到的,该算法计算单词的每个子串的最佳分段,我们将其存储在名为 `best_segmentations` 的变量中。我们将在单词的每个位置(从 0 到其总长度)存储一个字典,有两个键:最佳分割中最后一个标记的开始索引,以及最佳分割的分数。使用最后一个标记的开始索引,一旦列表完全填充,我们将能够检索完整的分段。 +现在,主函数是使用 Viterbi 算法对单词进行分词。像我们之前看到的那样,这个算法会计算出每个词的最好的分割方式,我们把这个结果保存在一个叫做 `best_segmentations` 的变量里。我们会为词的每一个位置(从 0 开始,一直到词的总长度)都保存一个字典,字典里有两个键:最好的分割中最后一个词的起始位置,以及最好的分割的得分。有了最后一个词的起始位置,当我们把整个列表都填满后,我们就能找到完整的分割方式。 -填充列表只需两个循环:主循环遍历每个起始位置,第二个循环尝试从该起始位置开始的所有子字符串。如果子串在词汇表中,我们有一个新的词分段,直到该结束位置,我们将其与 `best_segmentations` 相比较。 +我们只需要两个循环就可以填充这个列表:一个主循环用来遍历每个可能的开始位置,第二个循环则试着找出所有以这个开始位置开始的子串。如果这个子串在我们的词表里,那么我们就找到了一个新的分词方式,这个分词方式会在当前位置结束。然后,我们会把这个新的分词方式和 `best_segmentations` 里的内容进行比较。 -一旦主循环完成,我们就从结尾开始,从一个开始位置跳到下一个,记录我们前进的标记,直到我们到达单词的开头: +当主循环结束后,我们就从词的最后一个位置开始,然后一步步往前跳,跳过的每一步,我们都会记录下来,直到我们回到词的开头。 ```python def encode_word(word, model): @@ -242,13 +244,13 @@ def encode_word(word, model): {"start": None, "score": None} for _ in range(len(word)) ] for start_idx in range(len(word)): - # This should be properly filled by the previous steps of the loop + # best_score_at_start应该由循环的前面的步骤计算和填充 best_score_at_start = best_segmentations[start_idx]["score"] for end_idx in range(start_idx + 1, len(word) + 1): token = word[start_idx:end_idx] if token in model and best_score_at_start is not None: score = model[token] + best_score_at_start - # If we have found a better segmentation ending at end_idx, we update + # 如果我们发现以 end_idx 结尾的更好分段,我们会更新 if ( best_segmentations[end_idx]["score"] is None or best_segmentations[end_idx]["score"] > score @@ -257,7 +259,7 @@ def encode_word(word, model): segmentation = best_segmentations[-1] if segmentation["score"] is None: - # We did not find a tokenization of the word -> unknown + # 我们没有找到单词的 tokens -> unknown return [""], None score = segmentation["score"] @@ -273,7 +275,7 @@ def encode_word(word, model): return tokens, score ``` -我们已经可以在一些词上尝试我们的初始模型: +我们已经可以在一些词上尝试我们的初始模型: ```python print(encode_word("Hopefully", model)) @@ -285,7 +287,7 @@ print(encode_word("This", model)) (['This'], 6.288267030694535) ``` -现在很容易计算模型在语料库上的损失! +现在,计算语料库上的分词损失就很简单了! ```python def compute_loss(model): @@ -296,7 +298,7 @@ def compute_loss(model): return loss ``` -我们可以检查它是否适用于我们拥有的模型: +我们可以检查一下我们的模型是否有效: ```python compute_loss(model) @@ -306,7 +308,7 @@ compute_loss(model) 413.10377642940875 ``` -计算每个标记的分数也不是很难;我们只需要计算通过删除每个标记获得的模型的损失: +计算每个词的分数也并非难事;我们只需要计算通过删除每个词得到的模型的损失: ```python import copy @@ -316,7 +318,7 @@ def compute_scores(model): scores = {} model_loss = compute_loss(model) for token, score in model.items(): - # We always keep tokens of length 1 + # 我们将保留长度为 1 的 tokens if len(token) == 1: continue model_without_token = copy.deepcopy(model) @@ -325,7 +327,7 @@ def compute_scores(model): return scores ``` -我们可以在给定的标记上尝试: +我们可以试试看对于给定的词是否有效: ```python scores = compute_scores(model) @@ -333,7 +335,7 @@ print(scores["ll"]) print(scores["his"]) ``` -自从 `"ll"` 用于标记化 `"Hopefully"`, 删除它可能会让我们使用标记 `"l"` 两次相反,我们预计它将产生正损失。 `"his"` 仅在单词`"This"` 内使用,它被标记为自身,所以我们期望它的损失为零。结果如下: +因为 `"ll"` 这个子词在 `"Hopefully"` 这个词的分词中被使用了,如果我们把它删掉,我们可能会需要用两个 `"l"` 来代替,所以我们预计它会导致损失值增加。而 `"his"` 这个词只在 `"This"` 这个词里面被使用,而且 `"This"` 是作为一个完整的词被分割的,所以我们预计删除它的损失值变化会是零。下面就是实验结果: ```python out 6.376412403623874 @@ -342,18 +344,18 @@ print(scores["his"]) -💡 这种方法非常低效,因此 SentencePiece 使用了没有标记 X 的模型损失的近似值:它不是从头开始,而是通过其在剩余词汇表中的分段替换标记 X。这样,所有分数可以与模型损失同时计算。 +💡 这种方式效率非常低,所以 SentencePiece 使用了一种估算方法来计算如果没有 X token,模型的损失会是多少:它不是重新开始,而是只是用剩下的词表里 X token 的分词方式来替代它。这样,所有的得分都能在和模型损失一起的同时计算出来。 -完成所有这些后,我们需要做的最后一件事是将模型使用的特殊标记添加到词汇表中,然后循环直到我们从词汇表中修剪了足够的标记以达到我们想要的大小: +至此,我们需要做的最后一件事就是将模型使用的特殊 tokens 添加到词汇表中,然后循环直到我们从词汇表中剪除足够多的 tokens 以达到我们期望的规模: ```python percent_to_remove = 0.1 while len(model) > 100: scores = compute_scores(model) sorted_scores = sorted(scores.items(), key=lambda x: x[1]) - # Remove percent_to_remove tokens with the lowest scores. + # 删除分数最低的percent_to_remov tokens 。 for i in range(int(len(model) * percent_to_remove)): _ = token_freqs.pop(sorted_scores[i][0]) @@ -361,7 +363,7 @@ while len(model) > 100: model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -然后,为了标记一些文本,我们只需要应用预标记化,然后使用我们的 `encode_word()` 函数: +然后,要对某些文本进行 tokenization,我们只需进行预分词然后使用我们的 `encode_word()` 函数: ```python def tokenize(text, model): @@ -370,7 +372,6 @@ def tokenize(text, model): 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) ``` @@ -378,4 +379,4 @@ tokenize("This is the Hugging Face course.", model) ['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] ``` -Unigram 就是这样!希望现在你感觉自己是标记器所有方面的专家。在下一节中,我们将深入研究 🤗 Tokenizers 库的构建块,并向您展示如何使用它们来构建您自己的标记器。 +至此 Unigram 的介绍完毕!期望此刻你已感觉自身如同领域的专家一般。在下一节中,我们将深入探讨🤗Tokenizers 库的基本构造模块,并展示如何使用它们构建自己的 tokenizer \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index 73e07c7fa..2ff99a64b 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -1,4 +1,4 @@ -# 逐块地构建标记器 [[逐块地构建标记器]] +# 模块化构建 tokenizer [[模块化构建 tokenizer ]] -正如我们在前几节中看到的,标记化包括几个步骤: +正如我们在前几节中看到的,tokenization 包括几个步骤: -- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、Unicode 规范化等) -- 预标记化(将输入拆分为单词) -- 通过模型处理输入(使用预先拆分的词来生成一系列标记) -- 后处理(添加标记器的特殊标记,生成注意力掩码和标记类型 ID) +- 标准化(任何认为必要的文本清理,例如删除空格或重音符号、Unicode 规范化等) +- 预分词(将输入拆分为单词) +- 通过模型处理输入(使用预先拆分的词来生成一系列 tokens ) +- 后处理(添加 tokenizer 的特殊 tokens 生成注意力掩码和 token 类型 ID) -提醒一下,这里再看一下整个过程 +作为复习,这里再看一遍整个过程:
The tokenization pipeline.
-🤗 Tokenizers 库旨在为每个步骤提供多个选项,您可以将它们混合和匹配在一起。在本节中,我们将看到如何从头开始构建标记器,而不是像我们[第二节 2](/course/chapter6/2)那样从旧的标记器训练新的标记器.然后,您将能够构建您能想到的任何类型的标记器! +🤗 Tokenizers 库旨在为每个步骤提供多个选项,你可以任意搭配这些选项。在这一节中,我们将看到如何从零开始构建 tokenizer,而不是像我们在 [第二节](/course/chapter6/2) 中那样从旧的 tokenizer 训练新的 tokenizer 然后,你将能够构建任何你能想到的类型的 tokenizer -更准确地说,该库是围绕一个中央“Tokenizer”类构建的,构建这个类的每一部分可以在子模块的列表中重新组合: +更精确地说,这个库围绕一个中心的 `Tokenizer` 类,实现了组成 `Tokenizer` 的各种子模块: -- `normalizers` 包含你可以使用的所有可能的Normalizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/normalizers))。 -- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/pre-tokenizers))。 -- `models` 包含您可以使用的各种类型的Model,如BPE、WordPiece和Unigram(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/models))。 -- `trainers` 包含所有不同类型的 trainer,你可以使用一个语料库训练你的模型(每种模型一个;完整列表[在这里](https://huggingface.co/docs/tokenizers/api/trainers))。 -- `post_processors` 包含你可以使用的各种类型的PostProcessor(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/post-processors))。 -- `decoders` 包含各种类型的Decoder,可以用来解码标记化的输出(完整列表[在这里](https://huggingface.co/docs/tokenizers/components#decoders))。 +- `normalizers` 包含所有可能使用的 `Normalizer(标准化)` 模块(完整列表 [在这里](https://huggingface.co/docs/tokenizers/api/normalizers) )。 +- `pre_tokenizesr` 包含所有可能使用的 `PreTokenizer(预处理)` 模块(完整列表 [在这里](https://huggi ngface.co/docs/tokenizers/api/pre-tokenizers) )。 +- `models` 包含了你可以使用的各种 `Model(子词分词算法模型)` 模块,如 `BPE` 、 `WordPiece` 和 `Unigram` (完整列表 [在这里](https://huggingface.co/docs/tokenizers/api/models) )。 +- `trainers` 包含所有不同类型的 `trainer` ,你可以使用它们在语料库上训练你的模型(每种模型一个;完整列表 [在这里](https://huggingface.co/docs/tokenizers/api/trainers) )。 +- `post_processors` 包含你可以使用的各种类型的 `PostProcessor(后处理)` 模块,(完整列表 [在这里](https://huggingface.co/docs/tokenizers/api/post-processors) )。 +- `decoders` 包含各种类型的 `Decoder` ,可以用来解码 tokenization 后的输出(完整列表 [在这里](https://huggingface.co/docs/tokenizers/components#decoders) )。 -您可以[在这里](https://huggingface.co/docs/tokenizers/components)找到完整的模块列表。 +你可以 [在这里](https://huggingface.co/docs/tokenizers/components) 找到完整的模块列表。 ## 获取语​​料库 [[获取语​​料库]] -为了训练我们的新标记器,我们将使用一个小的文本语料库(因此示例运行得很快)。获取语​​料库的步骤与我们在[在这章的开始]((/course/chapter6/2)那一小节,但这次我们将使用[WikiText-2](https://huggingface.co/datasets/wikitext)数据集: +为了训练新的 tokenizer 我们将使用一小部分文本作为语料库(这样运行得更快)。获取语​​料库的步骤与我们在 [在这章的开头](/course/chapter6/2) 采取的步骤类似,但这次我们将使用 [WikiText-2](https://huggingface.co/datasets/wikitext) 数据集: ```python from datasets import load_dataset @@ -50,10 +50,9 @@ def get_training_corpus(): for i in range(0, len(dataset), 1000): yield dataset[i : i + 1000]["text"] ``` +`get_training_corpus()` 函数是一个生成器,每次调用的时候将产生 1,000 个文本,我们将用它来训练 tokenizer 。 -**get_training_corpus()** 函数是一个生成器,每次调用的时候将产生 1,000 个文本,我们将用它来训练标记器。 - -🤗 Tokenizers 也可以直接在文本文件上进行训练。以下是我们如何生成一个文本文件,其中包含我们可以在本地使用的来自 WikiText-2 的所有文本/输入: +🤗 Tokenizers 也可以直接在文本文件上进行训练。以下是我们生成一个包含 WikiText-2 所有文本的代码,这样我们就可以在本地离线使用: ```python with open("wikitext-2.txt", "w", encoding="utf-8") as f: @@ -61,13 +60,13 @@ with open("wikitext-2.txt", "w", encoding="utf-8") as f: f.write(dataset[i]["text"] + "\n") ``` -接下来,我们将向您展示如何逐块构建您自己的 BERT、GPT-2 和 XLNet 标记器。这将为我们提供三个主要标记化算法的示例:WordPiece、BPE 和 Unigram。让我们从 BERT 开始吧! +接下来,我们将展示如何模块化地构建你自己的 BERT、GPT-2 和 XLNet tokenizer 这将包含主要的分词算法:WordPiece、BPE 和 Unigram 的例子。让我们从 BERT 开始吧! -## 从头开始构建 WordPiece 标记器 [[从头开始构建 WordPiece 标记器]] +## 从头开始构建 WordPiece tokenizer [[从头开始构建 WordPiece tokenizer ]] -要使用 🤗 Tokenizers 库构建标记器,我们首先使用**model**实例化一个 **Tokenizer** 对象与 ,然后将 **normalizer** , **pre_tokenizer** , **post_processor** , 和 **decoder** 属性设置成我们想要的值。 +要用🤗 Tokenizers 库构建一个 tokenizer 我们首先实例化一个带有 `model` 的 `Tokenizer` 对象,然后将其 `normalizer` , `pre_tokenizer` , `post_processor` 和 `decoder` 属性设置为我们想要的值。 -对于这个例子,我们将创建一个 **Tokenizer** 使用 WordPiece 模型: +以这个例子来说,我们将创建一个使用 WordPiece 模型的 `Tokenizer` : ```python from tokenizers import ( @@ -83,15 +82,15 @@ from tokenizers import ( tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) ``` -我们必须指定 **unk_token** 这样模型才知道当它遇到以前没有见过的字符时要返回什么。我们可以在此处设置的其他参数包括我们模型的**vocab(字典)**(我们将训练模型,所以我们不需要设置它)和 **max_input_chars_per_word** 即每个单词的最大长度(比传递的值长的单词将被拆分) +我们必须指定 `unk_token` ,这样当模型遇到它从未见过的字符时,它就会返回 `unk_token`。我们在这里可以设置的其他参数包括已有的 `vocab(词汇表)` (我们要重新训练模型,所以我们不需要设置这个)和 `max_input_chars_per_word` ,它指定了每个词的最大长度(比 `max_input_chars_per_word` 长的词将被拆分)。 -标记化的第一步是规范化,所以让我们从它开始。 由于 BERT 被广泛使用,所以有一个可以使用的 `BertNormalizer`,我们可以为 BERT 设置经典的选项:`lowercase(小写)` 和 `strip_accents(去除音调)`,不言自明; `clean_text` 删除所有控制字符并将重复的空格替换为一个; 和 `handle_chinese_chars`,在汉字周围放置空格。 要实现 `bert-base-uncased` ,我们可以这样设置这个规范器: +tokenization 的第一步是标准化,所以我们从这里开始。由于 BERT 被广泛使用,所以我们可以使用 `BertNormalizer` ,我们可以为 BERT 设置经典参数: `lowercase(小写)` 和 `strip_accents(去除重音的字符)` , `clean_text` 用于删除所有控制字符并将重复的空格替换为一个空格;以及 `handle_chinese_chars` ,它将在中文字符周围添加空格。要复现 `bert-base-uncased` tokenizer 我们可以这样设置 `normalizer` : ```python tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) ``` -然而,一般来说,在构建新的标记器时,您可以使用已经在 🤗 Tokenizers库中实现的非常方便的normalizer——所以让我们看看如何手动创建 BERT normalizer。 该库提供了一个“Lowercase(小写)”的normalizer和一个“StripAccents”的normalizer,您可以使用“序列”组合多个normalizer: +然而,通常来说,当你构建一个新的 tokenizer 时,也需要同步构建一个新的 `normalizer` —— 所以我们来看看如何手动创建 `BERT normalizer` 。🤗 Tokenizers 库提供了一个 `Lowercase normalizer` 和一个 `StripAccents normalizer` ,并且你可以使用 Sequence 来组合多个 normalizer。 ```python tokenizer.normalizer = normalizers.Sequence( @@ -99,9 +98,9 @@ tokenizer.normalizer = normalizers.Sequence( ) ``` -我们也在使用 **NFD** Unicode normalizer,否则 **StripAccents** normalizer 无法正确识别带重音的字符,因此没办法删除它们。 +我们还使用了一个 `NFD Unicode normalizer` ,否则,否则 `StripAccents normalizer` 将因为无法正确识别带有重音的字符,从而没办法去除重音。 -正如我们之前看到的,我们可以使用 **normalize** 的 **normalize_str()** 方法查看它对给定文本的影响: +正如我们之前看到的,我们可以使用 `normalizer` 的 `normalize_str()` 方法来对它进行测试: ```python print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) @@ -113,12 +112,13 @@ hello how are u? -**更进一步**如果您在包含 unicode 字符的字符串上测试先前normalizers的两个版本,您肯定会注意到这两个normalizers并不完全等效。 -为了不过度使用 `normalizers.Sequence` 使版本过于复杂,我们没有包含当 `clean_text` 参数设置为 `True` 时 `BertNormalizer` 需要的正则表达式替换 - 这是默认行为。 但不要担心:通过在normalizer序列中添加两个 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情况下获得完全相同的规范化。 +**更进一步**如果你在包含 unicode 字符的字符串上测试先前 normalizers 的两个版本,你肯定会注意到这两个 normalizers 并不完全等效。 + +为了避免 `normalizers.Sequence` 过于复杂,我们的实现没有包含当 `clean_text` 参数设置为 `True` 时 `BertNormalizer` 需要的正则表达式替换 —— 而这是 `BertNormalizer` 默认会实现的。但不要担心:通过在 normalizer 序列中添加两个 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情况下获得完全相同的标准化。 -接下来是预标记步骤。 同样,我们可以使用一个预构建的“BertPreTokenizer”: +下一步是预分词。同样,我们可以使用预构建的 `BertPreTokenizer` : ```python tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() @@ -130,7 +130,7 @@ tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() ``` -请注意,`Whitespace` 预标记器会在空格和所有非字母、数字或下划线字符的字符上进行拆分,因此在本次的例子中上会根据空格和标点符号进行拆分: +注意, `Whitespace` 会使用空格和所有不是字母、数字或下划线的字符进行分割,因此在本次的例子中上会根据空格和标点符号进行分割: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") @@ -141,7 +141,7 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] ``` -如果您只想在空白处进行拆分,则应使用 **WhitespaceSplit** 代替预标记器: +如果你只想使用空格进行分割,则应该使用 `WhitespaceSplit` : ```python pre_tokenizer = pre_tokenizers.WhitespaceSplit() @@ -152,7 +152,7 @@ pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") [("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] ``` -像normalizers一样,您可以使用 **Sequence** 组成几个预标记器: +就像 normalizer 一样,你可以使用 `Sequence` 来组合几个预分词的步骤: ```python pre_tokenizer = pre_tokenizers.Sequence( @@ -166,29 +166,29 @@ pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] ``` -标记化管道的下一步是输入给模型。我们已经在初始化中指定了我们的模型,但我们仍然需要训练它,这将需要一个 **WordPieceTrainer** .在 🤗 Tokenizers 中实例化训练器时要记住的主要事情是,您需要将您打算使用的所有特殊标记传递给它 - 否则它不会将它们添加到词汇表中,因为它们不在训练语料库中: +tokenization 流程的下一步是将输入数据传递给模型。我们已经在初始化时指定了我们的模型,但是我们还需要对其进行训练,这就需要一个 `WordPieceTrainer` 。在实例化一个🤗 Tokenizers 中的 `Trainer` 时,一件很重要的事情是,你需要将你打算使用的所有特殊 tokens 都传递给它——否则,由于它们不在训练语料库中,`Trainer` 就不会将它们添加到词汇表中: ```python special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) ``` -以及指定 **vocab_size(词典大小)** 和 **special_tokens(特殊的标记)** ,我们可以设置 **min_frequency** (记号必须出现在词汇表中的次数)或更改 **continuing_subword_prefix** (如果我们想使用与 **##**指代存在与字词相同的前缀 )。 +除了指定 `vocab_size` 和 `special_tokens` ,我们还可以设置 `min_frequency` (一个 tokens 必须达到的最小的出现的次数才能被包含在词汇表中)或更改 `continuing_subword_prefix` (如果我们想使用其他的字符来替代 `##` )。 -要使用我们之前定义的迭代器训练我们的模型,我们只需要执行以下命令: +我们只需要执行以下命令就可以使用我们之前定义的迭代器训练我们的模型: ```python tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) ``` -我们还可以使用文本文件来训练我们的标记器,它看起来像这样(我们需要先初始化一个空的 **WordPiece** ): +我们还可以使用本地的文本文件来训练我们的 tokenizer 它看起来像这样(我们需要先使用 `WordPiece` 初始化一个空的模型): ```python tokenizer.model = models.WordPiece(unk_token="[UNK]") tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -在这两种情况下,我们都可以通过调用文本来测试标记器 **encode()** 方法: +在这两种情况下,我们都可以通过调用 `encode()` 方法来测试 tokenizer ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -199,9 +199,9 @@ print(encoding.tokens) ['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] ``` -这个 **encoding** 获得的是一个 **Encoding**对象 ,它的属性中包含标记器的所有必要输出: **ids** , **type_ids** , **tokens** , **offsets** , **attention_mask** , **special_tokens_mask** , 和 **overflowing** . +所得到的 `encoding` 是一个 `Encoding` 对象,它包含 tokenizer 的所有必要属性: `ids` 、 `type_ids` 、 `tokens` 、 `offsets` 、 `attention_mask` 、 `special_tokens_mask` 和 `overflowing` 。 -标记化管道的最后一步是后处理。我们需要添加 **[CLS]** 开头的标记和 **[SEP]** 标记在末尾(或在每个句子之后,如果我们有一对句子)。我们将使用一个 **TemplateProcessor** 为此,但首先我们需要知道 **[CLS]** 和 **[SEP]** 在词汇表中的ID: +tokenizer 管道的最后一步是后处理。我们需要在开头添加 `[CLS]` token,然后在结束时(或在每句话后,如果我们有一对句子)添加 `[SEP]` token。我们将使用 `TemplateProcessor` 来完成这个任务,但首先我们需要知道词汇表中 `[CLS]` 和 `[SEP]` tokens 的 ID: ```python cls_token_id = tokenizer.token_to_id("[CLS]") @@ -213,9 +213,9 @@ print(cls_token_id, sep_token_id) (2, 3) ``` -为了给 **TemplateProcessor** 编写模板,我们必须指定如何处理单个句子和一对句子。对于两者,我们都编写了我们想要使用的特殊标记;第一个(或单个)句子表示为 **$A** ,而第二个句子(如果对一对进行编码)表示为 **$B** .对于这些特殊标记和句子,我们还需要使用在冒号后指定相应的标记类型 ID。 +编写 `TemplateProcessor` 的模板时,我们必须指定如何处理单个句子和一对句子。对于这两者,我们写下我们想使用的特殊 tokens 第一句(或单句)用 `$A` 表示,而第二句(如果需要编码一对句子)用 `$B` 表示。对于这些(特殊 tokens 和句子),我们还需要在冒号后指定相应的 token 类型 ID。 -因此经典的 BERT 模板定义如下: +因此,经典的 BERT 模板定义如下: ```python tokenizer.post_processor = processors.TemplateProcessing( @@ -225,9 +225,9 @@ tokenizer.post_processor = processors.TemplateProcessing( ) ``` -请注意,我们需要传递特殊标记的 ID,以便标记器可以正确地将特殊标记转换为它们的 ID。 +请注意,我们需要传递特殊 tokens 的 ID,这样 tokenizer 才能正确地将它们转换为它们的 ID。 -添加后,我们之前的示例将输出出: +添加之后,我们再次对之前的例子进行 tokenization: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -238,7 +238,8 @@ print(encoding.tokens) ['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] ``` -在一对句子中,我们得到了正确的结果: +在一对句子中,我们也得到了正确的结果: + ```python encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") print(encoding.tokens) @@ -250,13 +251,13 @@ print(encoding.type_ids) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -我们几乎从头开始构建了这个标记器——但是还有最后一步是指定一个解码器: +我们几乎从头开始构建了这个 tokenizer ——但是还有最后一步:指定一个解码器: ```python tokenizer.decoder = decoders.WordPiece(prefix="##") ``` -让我们测试一下我们之前的 **encoding** : +让我们在之前的 `encoding` 上测试一下它: ```python tokenizer.decode(encoding.ids) @@ -266,28 +267,29 @@ tokenizer.decode(encoding.ids) "let's test this tokenizer... on a pair of sentences." ``` -很好!我们可以将标记器保存在一个 JSON 文件中,如下所示: +很好!我们可以将 tokenizer 保存在一个 JSON 文件中,如下所示: ```python tokenizer.save("tokenizer.json") ``` -然后我们可以使用**from_file()** 方法从该文件里重新加载 **Tokenizer** 对象: +然后,我们可以在一个 `Tokenizer` 对象中使用 `from_file()` 方法重新加载该文件: ```python new_tokenizer = Tokenizer.from_file("tokenizer.json") ``` -要在 🤗 Transformers 中使用这个标记器,我们必须将它包裹在一个 **PreTrainedTokenizerFast** 类中。我们可以使用泛型类,或者,如果我们的标记器对应于现有模型,则使用该类(例如这里的 **BertTokenizerFast** )。如果您应用本课来构建全新的标记器,则必须使用第一个选项。 +要在🤗 Transformers 中使用这个 tokenizer 我们需要将它封装在一个 `PreTrainedTokenizerFast` 类中。我们可以使用通用类(PreTrainedTokenizerFast),或者,如果我们的 tokenizer 对应于一个现有的模型,则可以使用该类(例如这里的 `BertTokenizerFast` )。如果你使用这个课程来构建一个全新的 tokenizer 并且没有一个现有的模型可以使用,就必须需要使用通用类。 + +要将构建的 tokenizer 封装在 `PreTrainedTokenizerFast` 类中,我们可以将我们构建的 tokenizer 作为 `tokenizer_object` 传入,或者将我们保存的 tokenizer 文件作为 `tokenizer_file` 传入。要记住的关键一点是,我们需要手动设置所有的特殊 tokens,因为这个类不能从 `tokenizer` 对象推断出哪个符号是掩码符号, `[CLS]` 符号等: -要将标记器包装在 `PreTrainedTokenizerFast` 类中,我们可以将我们构建的标记器作为`tokenizer_object` 传递,或者将我们保存为`tokenizer_file` 的标记器文件传递。 要记住的关键是我们必须手动设置所有特殊标记,因为该类无法从 `tokenizer` 对象推断出哪个标记是掩码标记、`[CLS]` 标记等: ```python from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( tokenizer_object=tokenizer, - # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively + # tokenizer_file="tokenizer.json", # 也可以从tokenizer文件中加载 unk_token="[UNK]", pad_token="[PAD]", cls_token="[CLS]", @@ -296,7 +298,7 @@ wrapped_tokenizer = PreTrainedTokenizerFast( ) ``` -如果您使用特定的标记器类(例如 **BertTokenizerFast** ),您只需要指定与默认标记不同的特殊标记(此处没有): +如果你使用的是其他的 tokenizer 类(如 `BertTokenizerFast` ),你只需要指定那些与默认值不同的特殊符号(这里没有): ```python from transformers import BertTokenizerFast @@ -304,27 +306,26 @@ from transformers import BertTokenizerFast wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) ``` -然后,您可以像使用任何其他 🤗 Transformers 标记器一样使用此标记器。你可以用 **save_pretrained()** 方法,或使用 **push_to_hub()** 方法。 +然后,你就可以像使用其他的🤗 Transformers tokenizer 一样使用这个 tokenizer 了。你可以使用 `save_pretrained()` 方法来保存它,或者使用 `push_to_hub()` 方法将它上传到 Hub。 -现在我们已经了解了如何构建 WordPiece 标记器,让我们对 BPE 标记器进行同样的操作。因为您已经知道了所有步骤,所以我们会进行地更快一点,并且只突出展示两者不一样的地方。 +既然我们已经看到了如何构建一个 WordPiece tokenizer。那么让我们也尝试构建 BPE tokenizer。这次我们会快一些,因为你已经知道所有的步骤,我们主要强调其中的区别。 -## 从头开始构建 BPE 标记器 [[从头开始构建 BPE 标记器]] +## 从头开始构建 BPE tokenizer [[从头开始构建 BPE tokenizer ]] -现在让我们构建一个 GPT-2 标记器。与 BERT 标记器一样,我们首先使用 **Tokenizer** 初始化一个BPE 模型: +现在让我们构建一个 GPT-2 tokenizer,与 BERT tokenizer 一样,我们首先通过 BPE model 初始化一个 `Tokenizer` : ```python tokenizer = Tokenizer(models.BPE()) ``` -和 BERT 一样,如果我们有一个词汇表,我们可以用一个词汇表来初始化这个模型(在这种情况下,我们需要传递 `vocab` 和 `merges`),但是由于我们将从头开始训练,所以我们不需要这样去做。 我们也不需要指定“unk_token”,因为 GPT-2 使用的字节级 BPE,不需要“unk_token”。 +同样,类似于 BERT,如果我们已经有一个词汇表,我们也可以使用这个词汇表来初始化 GPT 模型(在这种情况下,我们需要传入 `vocab` 和 `merges` 参数),但是因为我们将从头开始训练,所以我们不需要做这个。我们也不需要指定 `unk_token` ,因为 GPT-2 使用字节级 BPE,这不需要它。 -GPT-2 不使用归一化器,因此我们跳过该步骤并直接进入预标记化: +GPT-2 不使用 `normalizer` ,因此我们跳过该步骤并直接进入预分词: ```python tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) ``` - -我们在此处添加到 `ByteLevel` 的选项是不在句子开头添加空格(默认为ture)。 我们可以看一下使用这个标记器对之前示例文本的预标记: +我们在这里给 `ByteLevel` 添加的选项的含义是不在句子的开始添加空格(默认为 ture)。我们可以看一下之前的示例文本经过预分词后的结果: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") @@ -335,23 +336,23 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") ('tokenization', (15, 27)), ('!', (27, 28))] ``` -接下来是需要训练的模型。对于 GPT-2,唯一的特殊标记是文本结束标记: +接下来是需要训练的模型。对于 GPT-2,唯一的特殊符号是文本结束符: ```python trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) ``` -与 `WordPieceTrainer` 以及 `vocab_size` 和 `special_tokens` 一样,我们可以指定 `min_frequency` 如果我们愿意,或者如果我们有一个词尾后缀(如 `` ),我们可以使用 `end_of_word_suffix` 设置它。 +就像 `WordPieceTrainer` 一样,除了 `vocab_size` 和 `special_tokens` ,我们也可以设置 `min_frequency` ,或者如果我们需要添加一个词尾后缀(如 `` ),我们可以用 `end_of_word_suffix` 设置它。 -这个标记器也可以在文本文件上训练: +这个 tokenizer 也可以在本地的文本文件上训练: ```python tokenizer.model = models.BPE() tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -让我们看一下示例文本的标记化后的结果: +让我们看一下示例文本经过 tokenization 后的结果: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -362,13 +363,13 @@ print(encoding.tokens) ['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] ``` -我们对 GPT-2 标记器添加字节级后处理,如下所示: +我们对 GPT-2 tokenizer 添加字节级后处理,如下所示: ```python tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) ``` -`trim_offsets = False` 选项指示我们应该保留以 'Ġ' 开头的标记的偏移量:这样偏移量的开头将指向单词之前的空格,而不是第一个单词的字符(因为空格在技术上是标记的一部分)。 让我们看看我们刚刚编码的文本的结果,其中 `'Ġtest'` 是索引第 4 处的标记: +`trim_offsets = False` 这个选项告诉 post-processor,我们应该让那些以‘Ġ’开始的 tokens 的偏移量保持不变:这样,偏移量起始的索引将指向单词前的空格,而不是单词的第一个字符(因为空格在技术上是 token 的一部分)。让我们看一下我们编码示例文本的结果,其中 `'Ġtest'` 是索引 4 的 token ```python sentence = "Let's test this tokenizer." @@ -387,7 +388,7 @@ sentence[start:end] tokenizer.decoder = decoders.ByteLevel() ``` -我们可以仔细检查它是否正常工作: +我们可以再次检查它是否工作正常: ```python tokenizer.decode(encoding.ids) @@ -397,7 +398,7 @@ tokenizer.decode(encoding.ids) "Let's test this tokenizer." ``` -很好!现在我们完成了,我们可以像以前一样保存标记器,并将它包装在一个 **PreTrainedTokenizerFast** 或者 **GPT2TokenizerFast** 如果我们想在 🤗 Transformers中使用它: +太好了!现在我们完成了,我们可以像之前一样保存 tokenizer,并且如果我们想在🤗 Transformers 中使用它,可以将它封装在 `PreTrainedTokenizerFast` 类或者 `GPT2TokenizerFast` 类中: ```python from transformers import PreTrainedTokenizerFast @@ -417,11 +418,11 @@ from transformers import GPT2TokenizerFast wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) ``` -作为最后一个示例,我们将向您展示如何从头开始构建 Unigram 标记器。 +作为最后一个示例,我们将向你展示如何从零开始构建 Unigram tokenizer -## 从头开始构建 Unigram 标记器 [[从头开始构建 Unigram 标记器]] +## 从零开始构建 Unigram tokenizer [[从零开始构建 Unigram tokenizer ]] -现在让我们构建一个 XLNet 标记器。与之前的标记器一样,我们首先使用 Unigram 模型初始化一个 **Tokenizer** : +现在让我们构建一个 XLNet tokenizer 与之前的 tokenizer 一样,我们首先使用 Unigram model 初始化一个 `Tokenizer` : ```python tokenizer = Tokenizer(models.Unigram()) @@ -429,7 +430,7 @@ tokenizer = Tokenizer(models.Unigram()) 同样,如果我们有词汇表,我们可以用词汇表初始化这个模型。 -对于标准化,XLNet 使用了一些替换的方法(来自 SentencePiece): +在标准化步骤,XLNet 进行了一些替换(来自 SentencePiece 算法): ```python from tokenizers import Regex @@ -445,15 +446,15 @@ tokenizer.normalizer = normalizers.Sequence( ) ``` -这会取代 **“** 和 **”** 和 **”** 以及任何两个或多个空格与单个空格的序列,以及删除文本中的重音以进行标记。 +这会将``''替换为",将任何连续两个或更多的空格替换为一个空格,同时还将去掉待分词文本中的重音符号。 -用于任何 SentencePiece 标记器的预标记器是 `Metaspace`: +任何 SentencePiece tokenizer 使用的预 tokenizer 是 `Metaspace` : ```python tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() ``` -我们可以像以前一样查看示例文本的预标记化: +我们可以像以前一样查看示例文本的预分词: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") @@ -463,7 +464,7 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") [("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] ``` -接下来是需要训练的模型。 XLNet 有不少特殊的标记: +接下来是需要训练的模型。XLNet 有不少特殊的 tokens ```python special_tokens = ["", "", "", "", "", "", ""] @@ -473,16 +474,16 @@ trainer = trainers.UnigramTrainer( tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) ``` -不要忘记`UnigramTrainer` 的一个非常重要的参数是`unk_token`。 我们还可以传递特定于 Unigram 算法的其他参数,例如删除标记的每个步骤的“shrinking_factor(收缩因子)”(默认为 0.75)或指定给定标记的最大长度的“max_piece_length”(默认为 16) . +对于 `UnigramTrainer` 来说,一个非常重要的参数是 `unk_token` 。我们也可以传递一些 Unigram 算法独有的其他参数,例如我们可以设置每个删除 token 时的 `shrinking_factor` (默认为 0.75),或者指定 token 最大长度的 `max_piece_length` (默认为 16)。 -这个标记器也可以在文本文件上训练: +这个 tokenizer 也可以在本地的文本文件上训练: ```python tokenizer.model = models.Unigram() tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -让我们看一下示例文本的标记化后的结果: +让我们看一下示例文本的 tokenization 后的结果: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -493,8 +494,7 @@ print(encoding.tokens) ['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] ``` -A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: -XLNet 的一个特点是它将`` 标记放在句子的末尾,类型ID 为2(以将其与其他标记区分开来)。它会将结果填充在左侧。 我们可以使用模板处理所有特殊标记和标记类型 ID,例如 BERT,但首先我们必须获取 `` 和 `` 标记的 ID: +XLNet 的一个特点是它将 `` token 放在句子的末尾,token 类型 ID 为 2(以区别于其他 tokens)。因此,它在左边进行填充。我们可以像对待 BERT 一样,用模板处理所有特殊 tokens 和 tokens 类型 ID,但首先我们需要获取 `` 和 `` tokens 的 ID: ```python cls_token_id = tokenizer.token_to_id("") @@ -516,7 +516,7 @@ tokenizer.post_processor = processors.TemplateProcessing( ) ``` -我们可以通过编码一对句子来测试它的工作原理: +我们可以通过编码一对句子来测试它是否有效: ```python encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") @@ -530,13 +530,13 @@ print(encoding.type_ids) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] ``` -最后,我们添加一个 **Metaspace** 解码器: +最后,我们添加一个 `Metaspace` 解码器: ```python tokenizer.decoder = decoders.Metaspace() ``` -我们完成了这个标记器! 我们可以像以前一样保存标记器,如果我们想在 🤗 Transformers 中使用它,可以将它包装在 `PreTrainedTokenizerFast` 或 `XLNetTokenizerFast` 中。 使用 `PreTrainedTokenizerFast` 时要注意的一件事是,我们需要告诉🤗 Transformers 库应该在左侧填充特殊标记: +我们完成了这个 tokenizer。我们可以像保存其他 tokenizer 一样保存它。如果我们想在 🤗 Transformers 中使用它,可以将它封装在 `PreTrainedTokenizerFast` 类或 `XLNetTokenizerFast` 类中。使用 `PreTrainedTokenizerFast` 类时需要注意的一点是,除了特殊 tokens 之外,我们还需要告诉🤗 Transformers 库在左边进行填充: ```python from transformers import PreTrainedTokenizerFast @@ -561,4 +561,4 @@ from transformers import XLNetTokenizerFast wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) ``` -现在您已经了解了如何使用各种构建块来构建现有的标记器,您应该能够使用 🤗 tokenizer库编写您想要的任何标记器,并能够在 🤗 Transformers中使用它。 \ No newline at end of file +现在你已经了解了如何使用各种模块来构建现有的 tokenizer,你应该能够使用 🤗 tokenizer 库编写你想要的任何 tokenizer 并能够在 🤗 Transformers 中使用它。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/9.mdx b/chapters/zh-CN/chapter6/9.mdx index cb0c68169..61d507cdc 100644 --- a/chapters/zh-CN/chapter6/9.mdx +++ b/chapters/zh-CN/chapter6/9.mdx @@ -1,16 +1,16 @@ -# 标记器,回顾! [[标记器,回顾!]] +# tokenizer 回顾![[ tokenizer ,回顾!]] -完成这一章,辛苦了! +恭喜你完成了这一章! -在深入研究标记器之后,您应该: +在深入研究 tokenizer 之后,你应该: -- 能够使用旧的标记器作为模板来训练新的标记器 -- 了解如何使用偏移量将标记的位置映射到其原始文本范围 +- 能够使用旧的 tokenizer 作为模板来训练新的 tokenizer +- 了解如何使用偏移量将 tokens 的位置映射到其原始文本范围 - 了解 BPE、WordPiece 和 Unigram 之间的区别 -- 能够混合和匹配 🤗 Tokenizers 库提供的块来构建您自己的标记器 -- 能够在 🤗 Transformers 库中使用该标记器 \ No newline at end of file +- 能够混合使用 🤗 Tokenizers 库提供的块来构建你自己的 tokenizer +- 能够在 🤗 Transformers 库中使用该 tokenizer \ No newline at end of file