add second chapter

This commit is contained in:
skindhu 2024-10-25 12:55:54 +08:00
parent 041d12b6ad
commit 265759d6e2
6 changed files with 236 additions and 5 deletions

View File

@ -45,7 +45,7 @@
> [!TIP]
>
> 个人思考这里聊一下检索增强技术RAG目前已经广泛应用于特定领域的知识问答场景。尽管GPT在文本生成任务重表现强大但它们依赖的是预训练的知识这以为着它们的回答依赖于模型在预训练阶段学习到的信息。这就导致了几个问题
> **个人思考:** 这里聊一下检索增强技术RAG目前已经广泛应用于特定领域的知识问答场景。尽管GPT在文本生成任务重表现强大但它们依赖的是预训练的知识这以为着它们的回答依赖于模型在预训练阶段学习到的信息。这就导致了几个问题
>
> + **知识的有效性:** 模型的知识基于它的预训练数据因此无法获取最新的信息。比如GPT-3 的知识截止到 2021 年,无法回答最新的事件或发展。
> + **模型大小的限制:** 即使是大型模型,所能存储和运用的知识也是有限的。如果任务涉及特定领域(如医学、法律、科学研究),模型在预训练阶段可能没有涵盖足够的信息。
@ -65,7 +65,7 @@
本章接下来的部分将系统地介绍准备 LLM 使用的嵌入所需的步骤这些步骤包括将文本拆分为单词、将单词转换为token以及将token转化为嵌入向量。
## 2.2 文本分词
@ -73,9 +73,7 @@
<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`
我们即将用于 LLM 训练的文本数据集是一部由 Edith Wharton 创作的短篇小说《判决》,该作品已在网上公开,因此允许用于 LLM 训练任务。该文本可在 Wikisource 上找到,网址是 [https://en.wikisource.org/wiki/The_Verdict](https://en.wikisource.org/wiki/The_Verdict),您可以将其复制并粘贴到文本文件中。我已将其复制到名为 "the-verdict.txt" 的文本文件中,以便使用 Python 的标准文件读取工具进行加载。
```python
# Listing 2.1 Reading in a short story as text sample into Python
@ -85,5 +83,238 @@ print("Total number of character:", len(raw_text))
print(raw_text[:99])
```
另外,您可以在本书的 GitHub 仓库中找到名为 "the-verdict.txt" 的文件,网址是 [https://github.com/rasbt/LLMs-from-scratch/tree/main/ch02/01_main-chapter-code](https://github.com/rasbt/LLMs-from-scratch/tree/main/ch02/01_main-chapter-code)
便于演示的目的print命令输出文件的总字符数以及前100个字符。
```
Total number of character: 20479
I HAD always thought Jack Gisburn rather a cheap genius--though a good fellow enough--so
it was no
```
我们的目标是将这篇 20,479 个字符的短篇小说拆分为单词和特殊字符然后在接下来的章节中将这些token转换为 LLM 训练所需的嵌入。
> [!NOTE]
>
> **样本规模**
>
> 请注意,在处理 LLM 时,通常会处理数百万篇文章和数十万本书——也就是几 GB 的文本。然而,为了教学目的,使用像单本书这样的小文本样本就足够了,这样可以阐明文本处理步骤的主要思想,并能够在消费级硬件上合理地运行。
要如何做才能最好地拆分这段文本以获得token列表呢为此我们来进行一个小小的探讨使用 Python 的正则表达式库 re 进行说明。(请注意,您不需要学习或记住任何正则表达式语法,因为在本章后面我们将使用一个预构建的分词器。)
使用一些简单的示例文本,我们可以使用 re.split 命令,按照以下语法拆分文本中的空白字符:
```python
import re
text = "Hello, world. This, is a test."
result = re.split(r'(\s)', text)
print(result)
```
执行结果是一个包含单词、空白和标点符号的列表:
```
['Hello,', ' ', 'world.', ' ', 'This,', ' ', 'is', ' ', 'a', ' ', 'test.']
```
请注意,上述简单的分词方案仅仅用于将示例文本拆分为单个单词,然而有些单词仍然与我们希望单独列出的标点符号相连。我们也无需将所有文本转换为小写字母,因为大写字母有助于 LLM 区分专有名词和普通名词,理解句子结构,并学习生成正确的大写文本。
让我们修改正则表达式,将空白字符(\s、逗号和句点[,.])单独拆分出来:
```python
result = re.split(r'([,.]|\s)', text)
print(result)
```
我们可以看到,单词和标点符号现在已经成为单独一项,跟我们预期一致:
```
['Hello', ',', '', ' ', 'world', '.', '', ' ', 'This', ',', '', ' ', 'is', ' ', 'a', ' ', 'test', '.', '']
```
一个剩余的小问题是列表仍然包含空白字符。我们可以安全地按如下方式删除这些多余的字符:
```python
result = [item for item in result if item.strip()]
print(result)
```
```
['Hello', ',', 'world', '.', 'This', ',', 'is', 'a', 'test', '.']
```
> [!NOTE]
>
> **关于是否删除空白字符的探讨**
>
> 在开发一个简单的分词器时是否将空白字符编码为单独的字符或者直接将其删除取决于我们的应用和需求。删除空白字符可以减少内存和计算资源的消耗。然而如果我们训练的模型对文本的确切结构敏感例如Python 代码对缩进和空格非常敏感),那么保留空白字符就很有用。在这里,为了简化和缩短分词化输出,我们选择删除空白字符。稍后,我们将切换到一个包含空白字符的分词化方案。
我们上面设计的分词方案在简单的示例文本中表现良好。让我们进一步修改它,使其能够处理其他类型的标点符号,如问号、引号,以及在 Edith Wharton 短篇小说的前 100 个字符中看到的双破折号,还有其他特殊字符:
```python
text = "Hello, world. Is this-- a test?"
result = re.split(r'([,.:;?_!"()\']|--|\s)', text)
result = [item.strip() for item in result if item.strip()]
print(result)
```
执行后输出如下:
```
['Hello', ',', 'world', '.', 'Is', 'this', '--', 'a', 'test', '?']
```
如图 2.5 所示,我们的分词方案现在能够成功处理文本中的各种特殊字符。
<img src="../Image/chapter2/figure2.5.png" width="75%" />
现在我们已经有了一个基本的分词器,接下来让我们将其应用于艾迪丝·沃顿的整篇短篇小说:
```python
preprocessed = re.split(r'([,.:;?_!"()\']|--|\s)', raw_text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
print(len(preprocessed))
```
上述代码的输出是4690这是小说的token数量不包含空白字符
让我们检查一下前30个token
```python
print(preprocessed[:30])
```
生成的输出显示,我们的分词器似乎很好地处理了文本,因为所有单词和特殊字符都被很好地分开了:
```
['I', 'HAD', 'always', 'thought', 'Jack', 'Gisburn', 'rather', 'a', 'cheap', 'genius', '--', 'though', 'a', 'good', 'fellow', 'enough', '--', 'so', 'it', 'was', 'no', 'great', 'surprise', 'to', 'me', 'to', 'hear', 'that', ',', 'in']
```
## 2.3 将 tokens 转换为token IDs
在前一章节中我们将艾迪丝·华顿的短篇小说分词为单独的token。在本节中我们将把这些token从字符串转换为整形以生成所谓的token ID。这一步是将token ID 转换为嵌入向量的中间步骤。
为了将先前生成的token映射到token ID我们首先需要构建一个词汇表。这个词汇表定义了每个独特单词和特殊字符与唯一整数的映射如图 2.6 所示。
<img src="../Image/chapter2/figure2.6.png" width="75%" />
在前一章节中,我们将艾迪丝·华顿的短篇小说进行分词,并将其存储在名为 preprocessed 的 Python 变量中。现在让我们创建一个包含所有唯一token的列表并按字母顺序对其进行排序以确定词汇表的大小
```python
all_words = sorted(set(preprocessed))
vocab_size = len(all_words)
print(vocab_size)
```
在通过上述代码确定词汇表的大小为 1,130 后,我们通过以下代码创建词汇表并打印其前 51 个条目以便于说明:
```python
vocab = {token:integer for integer,token in enumerate(all_words)}
for i, item in enumerate(vocab.items()):
print(item)
if i > 50:
break
```
输出如下:
```
('!', 0)
('"', 1)
("'", 2)
...
('Her', 49)
('Hermia', 50)
```
根据输出可知词汇表包含了与唯一整数标签相关联的单个token。我们接下来的目标是利用这个词汇表将新文本转换为token ID如图 2.7 所示。
<img src="../Image/chapter2/figure2.7.png" width="75%" />
在本书后面,当我们想将 LLM 的输出从数字转换回文本时我们还需要一种将token ID 转换为文本的方法。为此我们可以创建一个词汇表的反向版本将token ID 映射回相应的文本token。
让我们在 Python 中实现一个完整的分词器类,其中包含一个 encode 方法该方法将文本拆分为token并通过词汇表进行token字符串到整数token ID的映射以通过词汇表生成token ID。此外我们还将实现一个 decode 方法该方法进行整数到字符串的反向映射将token ID 转换回文本。
该分词器的代码实现如下:
```python
# Listing 2.3 Implementing a simple text tokenizer
class SimpleTokenizerV1:
def __init__(self, vocab):
self.str_to_int = vocab #A
self.int_to_str = {i:s for s,i in vocab.items()} #B
def encode(self, text): #C
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids): #D
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text) #E
return text
#A 将词汇表作为类属性存储,以方便在 encode 和 decode 方法中访问
#B 创建一个反向词汇表将token ID 映射回原始的文本token
#C 将输入文本转换为token ID
#D 将token ID 还原为文本
#E 在指定的标点符号前去掉空格
```
使用上述的 SimpleTokenizerV1 Python 类,我们现在可以使用现有的词汇表实例化新的分词器对象,并利用这些对象对文本进行编码和解码,如图 2.8 所示。
<img src="../Image/chapter2/figure2.8.png" width="75%" />
让我们通过 SimpleTokenizerV1 类实例化一个新的分词器对象,并对艾迪丝·华顿的短篇小说中的一段文本进行分词,以便在实践中进行尝试:
```python
tokenizer = SimpleTokenizerV1(vocab)
text = """"It's the last he painted, you know," Mrs. Gisburn said with pardonable pride."""
ids = tokenizer.encode(text)
print(ids)
```
上面的代码打印出以下token ID
下来,让我们看看能否通过 decode 方法将这些token ID 转换回文本:
```python
print(tokenizer.decode(ids))
```
输出如下:
```
'" It\' s the last he painted, you know," Mrs. Gisburn said with pardonable pride.'
```
根据以上的输出,我们可以看到 decode 方法成功将token ID 转换回了原始文本。
到目前为止,一切都很顺利。我们实现了一个分词器,能够根据训练集中的片段对文本进行分词和去分词。现在让我们将其应用于训练集中未包含的新文本样本:
```python
text = "Hello, do you like tea?"
print(tokenizer.encode(text))
```
执行上述代码将导致以下错误:
```
...
KeyError: 'Hello'
```
问题在于短篇小说《裁决》中没有使用“Hello”这个词。因此它不包含在词汇中。这突显了在处理大型语言模型时需要考虑大型和多样化的训练集以扩展词汇的必要性。
在下一节中我们将进一步测试分词器在包含未知词汇的文本上的表现并且我们还将讨论可以用于在训练期间为LLM提供更多上下文的额外特殊tokens。
## 2.4 添加特殊上下文tokens

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

BIN
Image/image2.8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB