本章涵盖以下内容：

+ **为大语言模型的训练准备文本数据集**
+ **将文本分割成词和子词token**
+ **字节对编码（Byte Pair Encoding，BPE）：一种更为高级的文本分词技术**
+ **使用滑动窗口方法采样训练示例**
+ **将tokens转换为向量，输入到大语言模型中**



在上一章中，我们介绍了大语言模型（LLMs）的基本结构，并了解到它们基于海量的文本数据集进行预训练。我们特别关注的是仅使用通用 Transformer 架构中解码器部分的 LLMs，这也是 ChatGPT 和其他流行的类似 GPT 的 LLM 所依赖的模型。

在预训练阶段，LLMs 逐字处理文本。通过使用下一个单词预测任务训练拥有数百万到数十亿参数的 LLMs，最终能够生成具有出色能力的模型。这些模型随后可以进一步微调，以遵循指令或执行特定目标任务。然而，在我们接下来几章中实现和训练 LLMs 之前，我们需要准备训练数据集，这也是本章的重点，如图 2.1 所示。

<img src="../Image/chapter2/figure2.1.png" width="75%" />

在本章中，您将学习如何为训练 LLM 准备输入文本。这包括将文本拆分为单个单词和子词token，并将这些token编码为 LLM 的向量表示。您还将了解一些先进的token分割方案，比如字节对编码，这种方法在像 GPT 这样的流行 LLM 中得到应用。最后，我们将实现一个采样和数据加载策略，以生成后续章节中训练 LLM 所需的输入输出对。

  

## 2.1 理解词嵌入

深度神经网络模型，包括 LLM，往往无法直接处理原始文本。这是因为文本是分类数据，它与实现和训练神经网络所需的数学运算不兼容。因此，我们需要一种方法将单词表示为连续值向量。（对计算中向量和张量不熟悉的读者，可以在附录 A 的 A2.2 节中了解更多关于张量的内容。）

将数据转换为向量格式的过程通常被称为嵌入（embedding）。我们可以通过特定的神经网络层或其他预训练的神经网络模型来对不同类型的数据进行嵌入，比如视频、音频和文本，如图 2.2 所示。

<img src="../Image/chapter2/figure2.2.png" width="75%" />

如图 2.2 所示，我们可以使用嵌入模型来处理多种不同的数据格式。然而，值得注意的是，不同的数据格式需要使用不同的嵌入模型。例如，专为文本设计的嵌入模型并不适用于音频或视频数据的嵌入。

> [!TIP]
>
> **个人思考：** 不同格式的数据源（如文本、图像、音频、视频）在处理和嵌入时，需要不同的模型和技术，原因在于它们的数据结构、特征和处理方式各不相同，因此需要针对性的方法将这些不同的数据类型转换为适合神经网络处理的向量表示。以下总结了不同数据源在嵌入时的一些区别：
>
> | 数据类型 |          数据特征          |              嵌入模型              |          主要特征          |
> | :------: | :------------------------: | :--------------------------------: | :------------------------: |
> |   文本   |  离散的、序列化的符号数据  |   Word2Vec, GloVe, BERT, GPT 等    |    语义关系、上下文理解    |
> |   图像   | 二维像素网格，具有空间特征 |      CNN（ResNet、VGG）、ViT       | 形状、纹理、颜色等视觉特征 |
> |   音频   |        一维时序信号        |    CNN+频谱图、RNN、Transformer    |    频率、音调、时序依赖    |
> |   视频   |        时空序列数据        | 3D CNN、RNN+CNN、Video Transformer |     时空特征、动作捕捉     |

嵌入的本质是将离散对象（如单词、图像或整个文档）映射到连续向量空间中的点。嵌入的主要目的是将非数值数据转换为神经网络能够处理的格式。

虽然单词嵌入是最常用的文本嵌入形式，但也存在句子、段落或整篇文档的嵌入。句子和段落嵌入常被用于检索增强生成技术。检索增强生成结合了文本生成与从外部知识库中检索相关信息的过程，这是一种超出本书讨论范围的技术。由于我们希望训练类似于GPT的LLM，这些模型以逐字的方式生成文本，因此本章将重点放在单词嵌入上。

> [!TIP]
>
> 个人思考：这里聊一下检索增强技术（RAG），目前已经广泛应用于特定领域的知识问答场景。尽管GPT在文本生成任务重表现强大，但它们依赖的是预训练的知识，这以为着它们的回答依赖于模型在预训练阶段学习到的信息。这就导致了几个问题：
>
> + **知识的有效性：** 模型的知识基于它的预训练数据，因此无法获取最新的信息。比如，GPT-3 的知识截止到 2021 年，无法回答最新的事件或发展。
> + **模型大小的限制：** 即使是大型模型，所能存储和运用的知识也是有限的。如果任务涉及特定领域（如医学、法律、科学研究），模型在预训练阶段可能没有涵盖足够的信息。
> + **生成的准确性：** 生成模型可能会凭空编造信息（即“幻觉现象”），导致生成内容不准确或虚假。
>
> 而检索增强技术正是为了解决上述不足，它大致原理为将外部知识库（如文档、数据库、互联网等）进行向量化后存入到向量数据库中。当用户提交一个查询时，首先将这个查询也编码成一个向量，然后去承载外部知识库的向量数据种检索（检索技术有很多种）与问题相关的信息。检索到的信息被作为额外的上下文信息输入到LLM中，LLM会将这些外部信息与原始输入结合起来，以更准确和丰富的内容生成回答。想要进一步了解RAG技术及其应用，可以参考：[RAG 专区](https://waytoagi.feishu.cn/wiki/PUUfwNkwqielBOkbO5RcjnTQnUd)

生成单词嵌入的算法和框架有很多。其中，Word2Vec是较早且最受欢迎的项目之一。Word2Vec通过预测给定目标词的上下文或反之，训练神经网络架构以生成单词嵌入。Word2Vec的核心思想是，出现在相似上下文中的词通常具有相似的含义。因此，当将单词投影到二维空间进行可视化时，可以看到相似的词汇聚在一起，如图2.3所示。

<img src="../Image/chapter2/figure2.3.png" width="75%" />

词嵌入可以具有不同的维度，从一维到数千维。如图2.3所示，我们可以选择二维词嵌入进行可视化。更高的维度可能捕捉到更细微的关系，但代价是计算效率的降低。

虽然我们可以使用预训练模型（例如 Word2Vec）为机器学习模型生成嵌入，但 LLMs 通常会生成自己的嵌入，这些嵌入是输入层的一部分，并在训练过程中进行更新。将嵌入作为 LLM 训练的一部分进行优化，而不直接使用 Word2Vec，有一个明确的优势，就是嵌入能够针对特定的任务和数据进行优化。我们将在本章后面实现这样的嵌入层。此外，LLMs 还能够创建上下文化的输出嵌入，这一点我们将在第三章中讨论。

高维嵌入在可视化中面临挑战，因为我们的感官感知和常见的图形表示本质上只限于三维或更少的维度，这也是图 2.3 采用二维散点图展示二维嵌入的原因。然而，在处理 LLMs 时，我们通常使用的嵌入的维度远高于图 2.3 所示的维度。对于 GPT-2 和 GPT-3，嵌入的大小（通常称为模型隐状态的维度）会根据具体的模型变体和大小而有所不同。这是性能与效率之间的权衡。以具体示例为例，最小的 GPT-2 模型（117M 和 125M 参数）使用 768 维的嵌入大小，而最大的 GPT-3 模型（175B 参数）则使用 12,288 维的嵌入大小。

本章接下来的部分将系统地介绍准备 LLM 使用的嵌入所需的步骤，这些步骤包括将文本拆分为单词、将单词转换为token，以及将token转化为嵌入向量。

  

## 2.2 文本分词

本节将讨论如何将输入文本拆分为单个token，这是创建 LLM 嵌入所需的预处理步骤。这些token可以是单个单词或特殊字符，包括标点符号，具体如图 2.4 所示。

<img src="../Image/chapter2/figure2.4.png" width="75%" />

我们即将用于 LLM 训练的文本数据集是一部由 Edith Wharton 创作的短篇小说《判决》，该作品已在网上公开，因此允许用于 LLM 训练任务。该文本可在 Wikisource 上找到，网址是 https://en.wikisource.org/wiki/The_Verdict，您可以将其复制并粘贴到文本文件中。我已将其复制到名为 "the-verdict.txt" 的文本文件中，以便使用 Python 的标准文件读取工具进行加载。

`#000051 Listing 2.1 Reading in a short story as text sample into Python`

```python
# Listing 2.1 Reading in a short story as text sample into Python
with open("the-verdict.txt", "r", encoding="utf-8") as f:
		raw_text = f.read() 
print("Total number of character:", len(raw_text)) 
print(raw_text[:99])
```



