• Build a Large Language Model (From Scratch) 从头开始构建大型语言模型(第二章)学习笔记(上)


    2 理解大型语言模型(Working with Text Data)

    本章涵盖

    • 为大型语言模型训练准备文本
    • 将文本拆分为单词和子单词tokens
    • 字节对编码作为一种更高级的文本tokenizing方式
    • 使用滑动窗口方法对训练示例进行采样
    • 将tokens转换为输入大型语言模型的向量

    在上一章中,我们深入研究了大型语言模型(LLM)的一般结构,并了解到它们是在大量文本上进行预训练的。具体来说,我们的重点是基于 Transformer 架构的纯解码器 LLM,该架构是 ChatGPT 和其他流行的类似 GPT 的 LLM 中使用的模型的基础。

    在预训练阶段,LLM一次处理一个单词的文本。使用下一个单词预测任务训练具有数百万到数十亿个参数的LLM,可以产生具有令人印象深刻的功能的模型。然后可以进一步微调这些模型以遵循一般指令或执行特定的目标任务。但在我们在接下来的章节中实现和训练 LLM 之前,我们需要准备训练数据集,这是本章的重点如图 2.1 所示

    图 2.1 编码LLM(大型语言模型)的三个主要阶段的心智模型,先在一个通用文本数据集上对LLM进行预训练,然后在一个标注过的数据集上进行微调。本章将解释并编写数据准备和采样管道的代码,该管道为预训练提供给LLM的文本数据。。

    在本章中,您将学习如何为训练LLM准备输入文本。这涉及将文本拆分为单独的单词和子词tokens,然后将其编码为 LLM 的向量表示。您还将了解高级tokenization方案,例如字节对编码,该方案在 GPT 等流行的 LLM 中使用。最后,我们将实施采样和数据加载策略,以生成后续章节中训练LLM所需的输入输出对。

    2.1 理解词嵌入(Understanding word embeddings)

    深度神经网络模型(包括LLM)无法直接处理原始文本。由于文本是分类的,因此它与用于实现和训练神经网络的数学运算不兼容。因此,我们需要一种将单词表示为连续值向量的方法。

    将数据转换为矢量格式的概念通常称为嵌入。使用特定的神经网络层或另一个预训练的神经网络模型,我们可以嵌入不同的数据类型,例如视频、音频和文本,如图2.2所示。

    图 2.2 深度学习模型无法处理原始形式的视频、音频和文本等数据格式。因此,我们使用==嵌入模型==将这些原始数据转换为深度学习架构可以轻松理解和处理的==密集向量==表示。具体来说,该图说明了将原始数据转换为三维数值向量的过程。

    图2.2所示,我们可以通过嵌入模型处理各种不同的数据格式。然而,值得注意的是,不同的数据格式需要不同的嵌入模型。例如,为文本设计的嵌入模型不适合嵌入音频或视频数据。

    嵌入的核心是从离散对象(例如单词、图像甚至整个文档)到连续向量空间中的点的映射——嵌入的主要目的是将非数字数据转换为神经网络可以识别的格式。网络可以处理。

    虽然词嵌入是最常见的文本嵌入形式,但也有句子、段落或整个文档的嵌入。句子或段落嵌入是检索增强生成的流行选择。检索增强生成将生成(如生成文本)与检索(如搜索外部知识库)结合起来,在生成文本时提取相关信息,这是一种超出了本书范围的技术。由于我们的目标是训练类似 GPT 的 LLM,它学习一次生成一个单词的文本,因此本章重点讨论单词嵌入。

    已经开发了多种算法和框架来生成词嵌入。早期且最流行的示例之一是Word2Vec方法。 Word2Vec 训练神经网络架构,通过预测给定目标单词的单词上下文来生成单词嵌入,反之亦然。 Word2Vec 背后的主要思想是,出现在相似上下文中的单词往往具有相似的含义。因此,当出于可视化目的投影到二维词嵌入时,可以看到相似的术语聚集在一起,如图 2.3 所示。

    如果词嵌入是二维的,我们可以将它们绘制在二维散点图中以实现可视化目的,如图 2.3所示。当使用单词嵌入技术(例如 Word2Vec)时,与相似概念相对应的单词通常在嵌入空间中彼此靠近出现。例如,与国家和城市相比,不同类型的鸟类在嵌入空间中显得彼此更接近。

    词嵌入可以有不同的维度,从一到数千。如图2.3所示,我们可以选择二维词嵌入来实现可视化目的。更高的维度可能会捕获更细微的关系,但代价是计算效率。

    虽然我们可以使用 Word2Vec 等预训练模型来生成机器学习模型的嵌入,但LLM通常会生成自己的嵌入,这些嵌入是输入层的一部分,并在训练期间更新。在 LLM 训练中优化嵌入而不是使用 Word2Vec 的优点是嵌入针对特定任务和手头的数据进行了优化。我们将在本章后面实现这样的嵌入层。此外,正如我们在第 3 章中讨论的,LLM还可以创建上下文理解的输出嵌入

    不幸的是,高维嵌入对可视化提出了挑战,因为我们的感官知觉和常见的图形表示本质上仅限于三个维度或更少,这就是为什么图 2.3 在二维散点图中显示了二维嵌入。然而,在使用 LLM 时,我们通常使用比图 2.3 所示更高维度的嵌入。对于 GPT-2 和 GPT-3,嵌入大小(通常称为模型隐藏状态的维度)根据特定模型变体和大小而变化。这是性能和效率之间的权衡。最小的 GPT-2 模型(117M 和 125M 参数)使用 768 维的嵌入大小来提供具体示例。最大的 GPT-3 模型(175B 参数)使用 12,288 维的嵌入大小。

    本章接下来的部分将介绍准备 LLM 使用的嵌入所需的步骤,其中包括将文本拆分为单词、将单词转换为tokens以及将tokens转换为嵌入向量。

    2.2 文本Tokenizing(Tokenizing text)

    本节介绍我们如何将输入文本拆分为单独的tokens,这是为 LLM 创建嵌入所需的预处理步骤。这些tokens要么是单个单词,要么是特殊字符,包括标点符号,如图 2.4 所示。

    图 2.4 本节在LLM背景下涵盖的文本处理步骤的视图。在这里,我们将输入文本分割成单独的tokens,这些tokens可以是单词或特殊字符,例如标点符号。在接下来的部分中,我们将把文本转换为tokens ID 并创建tokens嵌入。

    我们将为 LLM 训练tokenize的文本是 Edith Wharton 的短篇小说《The Verdict》,该小说已发布到公共领域,因此允许用于 LLM 训练任务。该文本可在 Wikisource 上找到,网址为https://en.wikisource.org/wiki/The_Verdict,您可以将其复制并粘贴到文本文件中,我将其复制到文本文件中"the-verdict.txt" 以使用 Python 的标准文件读取实用程序进行加载:

    # 清单 2.1 将一个短篇故事作为文本示例读入 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])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或者,您可以在本书的 GitHub 存储库中找到此“the-verdict.txt" 文件: https://github.com/rasbt/LLMs-from-scratch/tree/main/ch02/01_main-chapter-code
    出于说明目的,打印命令打印该文件的总字符数,后跟该文件的前 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 个字符的短篇故事tokenize为单个单词和特殊字符,然后我们可以将其转化为后续章节中 LLM 训练的嵌入。

    文本样本大小
    请注意,在与LLM合作时,处理数百万篇文章和数十万本书(数千兆字节的文本)是很常见的。然而,出于教育目的,使用较小的文本样本(例如一本书)就足以说明文本处理步骤背后的主要思想,并使其能够在合理的时间内在消费类硬件上运行。

    我们怎样才能最好地分割这个文本以获得tokens列表?为此,我们进行了一次小游览,并使用 Python 的正则表达式库re进行说明。(请注意,您不必学习或记住任何正则表达式语法,因为我们将在本章后面过渡到预构建的分词器。)

    使用一些简单的示例文本,我们可以使用re.split具有以下语法的命令来根据空白字符拆分文本:

    正则表达式r’(\s)':这里\s代表空白字符(例如空格、换行符等)。圆括号()表示捕获组,意味着在分割的同时,匹配到的空白字符也会作为独立的元素包含在结果列表中。

    
    import re
    
    text = "Hello, world. This, is a test."
    result = re.split(r'(\s)', text)
    
    print(result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果是单个单词、空格和标点符号的列表:
    ‘Hello,’
    ’ ’
    ‘world.’
    ’ ’

    ['Hello,', ' ', 'world.', ' ', 'This,', ' ', 'is', ' ', 'a', ' ', 'test.']

    请注意,上面的简单tokenization方案主要用于将示例文本分成单独的单词,但是,某些单词仍然连接到我们希望作为单独列表条目的标点符号。我们也避免将所有文本都小写,因为大写有助于LLM区分专有名词和普通名词,理解句子结构,并学习生成具有正确大写的文本。

    让我们修改按空格 ( \s) 和逗号以及句点 ( [,.])分隔的正则表达式:

    在正则表达式 r’([,.]|\s)’ 中:
    [,.] 指的是一个字符集,匹配任何一个逗号 , 或者句号 .。
    | 是一个逻辑或操作符,表示匹配左边的 [,.] 或者右边的 \s。
    \s 匹配任何空白字符,包括空格、制表符、换行符等。
    圆括号 () 表示捕获组,这意味着在使用 re.split 进行分割时,匹配到的逗号、句号或空白字符也会作为分割结果的一部分返回

    result = re.split(r'([,.]|\s)', text)
    
    print(result)
    
    • 1
    • 2
    • 3

    我们可以看到单词和标点符号现在是单独的列表条目,正如我们想要的那样:

    ‘Hello’
    ‘,’
    ’ ’
    ‘world’

    ['Hello', ',', '', ' ', 'world', '.', '', ' ', 'This', ',', '', ' ', 'is', ' ', 'a', ' ', 'test', '.', '']

    剩下的一个小问题是列表仍然包含空白字符。或者,我们可以安全地删除这些冗余字符,如下所示:

    # Strip whitespace from each item and then filter out any empty strings.从每个项中去除空格,然后过滤掉任何空字符串。
    result = [item for item in result if item.strip()]
    print(result)
    
    • 1
    • 2
    • 3

    生成的无空格输出如下所示:

    ['Hello', ',', 'world', '.', 'This', ',', 'is', 'a', 'test', '.']

    是否删除空格
    在开发简单的tokenizer时,我们是否应该将空格编码为单独的字符,或者只是删除它们取决于我们的应用程序及其要求。删除空格可以减少内存和计算需求。但是,如果我们训练对文本的确切结构敏感的模型(例如,对缩进和间距敏感的 Python 代码),保留空格可能会很有用。在这里,为了tokenized输出的简单性和简洁性,我们删除了空格。稍后,我们将切换到包含空格的tokenization方案。

    我们上面设计的tokenization方案在简单的示例文本上效果很好。让我们进一步修改它,以便它还可以处理其他类型的标点符号,例如问号、引号和我们之前在Edith Wharton短篇小说的前 100 个字符中看到的双破折号,以及其他特殊字符:

    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)
    
    • 1
    • 2
    • 3
    • 4

    结果输出如下:

    ['Hello', ',', 'world', '.', 'Is', 'this', '--', 'a', 'test', '?']

    图 2.5 中总结的结果可以看出,我们的tokenization方案现在可以成功处理文本中的各种特殊字符。

    图 2.5 到目前为止,我们实现的tokenization方案将文本拆分为单独的单词和标点符号。在此图所示的具体示例中,示例文本被分为== 10 个单独的tokens==。

    现在我们已经有了一个基本的tokenizer(分词器),让我们将它应用到Edith Wharton的整个短篇小说中:

    preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text)
    preprocessed = [item.strip() for item in preprocessed if item.strip()]
    print(len(preprocessed))
    print(preprocessed[:30])
    
    • 1
    • 2
    • 3
    • 4

    上面的 print 语句输出4649,它是该文本中的tokens数(不包含空格)。

    让我们打印前 30 个tokens以进行快速目视检查:

    ['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']

    结果输出显示我们的tokenizer(分词器)似乎能够很好地处理文本,因为所有单词和特殊字符都被整齐地分开

    2.3 将tokens转换为tokensIDs (Converting tokens into token IDs)

    在上一节中,我们将Edith Wharton的短篇小说tokenized为单独的tokens。在本节中,我们会将这些tokens从 Python 字符串转换为整数表示形式,以生成所谓的token IDs。此转换是将token ID 转换为embedding vectors之前的中间步骤。

    为了将之前生成的 tokens 映射到 token IDs,我们必须首先构建一个所谓的vocabulary。该词汇表定义了我们如何将每个唯一单词和特殊字符映射到唯一整数,如图 2.6 所示。

    图 2.6 我们通过将训练数据集中的整个文本tokenizing为单独的tokens来构建词汇表。然后将这些单独的tokens按字母顺序排序,并删除重复的tokens。然后,将唯一token聚合到词汇表中,该词汇表定义从每个唯一token到唯一整数值​​的映射。为了说明的目的,所描绘的词汇量故意较小,并且为了简单起见不包含标点符号或特殊字符。

    在上一节中,我们对 Edith Wharton 的短篇小说进行了tokenized,并将其分配给一个名为 的 Python 变量preprocessed。现在让我们创建所有唯一tokens的列表,并按字母顺序对它们进行排序以确定词汇表大小:

    all_words = sorted(list(set(preprocessed)))
    vocab_size = len(all_words)
    
    print(vocab_size)
    
    • 1
    • 2
    • 3
    • 4

    1159

    通过上述代码确定词汇表大小为 1,159 后,我们创建词汇表并打印其前 50 个条目以供说明:

    注:set集合会去重

    vocab = {token:integer for integer,token in enumerate(all_words)}
    
    for i, item in enumerate(vocab.items()):
        print(item)
        if i >= 50:
            break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ('!', 0) ('"', 1) ("'", 2) ('(', 3) (')', 4) (',', 5) ('--', 6) ('.', 7) (':', 8) (';', 9) ('?', 10) ('A', 11) ('Ah', 12) ('Among', 13) ('And', 14) ('Are', 15) ('Arrt', 16) ('As', 17) ('At', 18) ('Be', 19) ('Begin', 20) ('Burlington', 21) ('But', 22) ('By', 23) ('Carlo', 24) ('Carlo;', 25) ('Chicago', 26) ('Claude', 27) ('Come', 28) ('Croft', 29) ('Destroyed', 30) ('Devonshire', 31) ('Don', 32) ('Dubarry', 33) ('Emperors', 34) ('Florence', 35) ('For', 36) ('Gallery', 37) ('Gideon', 38) ('Gisburn', 39) ('Gisburns', 40) ('Grafton', 41) ('Greek', 42) ('Grindle', 43) ('Grindle:', 44) ('Grindles', 45) ('HAD', 46) ('Had', 47) ('Hang', 48) ('Has', 49) ('He', 50)

    正如我们所看到的,根据上面的输出,字典包含与唯一整数标签关联的各个tokens。我们的下一个目标是应用这个词汇表将新文本转换为token IDs,如图 2.7 所示。

    图 2.7 从一个新的文本样本开始,我们对文本进行tokenize,并使用词汇表将文本tokens转换为token IDs。词汇表是根据整个训练集构建的,可以应用于训练集本身和任何新的文本样本。为了简单起见,所描述的词汇不包含标点符号或特殊字符。

    在本书后面,当我们想要将 LLM 的输出从数字转换回文本时,我们还需要一种将token IDs 转换为文本的方法。为此,我们可以创建词汇表的逆版本,将token IDs 映射回相应的文本tokens。

    让我们在 Python 中实现一个完整的tokenizer类,该类使用encode将文本拆分为tokens并执行字符串到整数的映射以通过词汇表生成token IDs 的方法。此外,我们还实现了一种decode执行反向整数到字符串映射的方法,将token IDs 转换回文本。

    该tokenizer实现的代码如下所示:

    class SimpleTokenizerV1:
        def __init__(self, vocab):
            self.str_to_int = vocab
            self.int_to_str = {i:s for s,i in vocab.items()}
        
        def encode(self, text):
            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):
            text = " ".join([self.int_to_str[i] for i in ids])
            # Replace spaces before the specified punctuations
            text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
            return text
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用SimpleTokenizerV1 上面的 Python 类,我们现在可以通过现有词汇表实例化新的tokenizer对象,然后使用该对象对文本进行编码和解码,如图 2.8 所示。

    1.The encode function turns text into token IDs
    2.The decode function turns token IDs back into text

    图 2.8 Tokenizer 实现共享两个通用方法:编码方法和解码方法。编码方法接收示例文本,将其分割成单独的token,并通过词汇表将tokens转换为token IDs。解码方法接收token IDs,将它们转换回文本令牌,并将文本令牌连接成自然文本。

    让我们从SimpleTokenizerV1类中实例化一个新的tokenizer对象,并对 Edith Wharton 的短篇小说中的一段进行tokenize,以便在实践中进行尝试:

    tokenizer = SimpleTokenizerV1(vocab)
    
    text = """"It's the last he painted, you know," Mrs. Gisburn said with pardonable pride."""
    ids = tokenizer.encode(text)
    print(ids)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [1, 58, 2, 872, 1013, 615, 541, 763, 5, 1155, 608, 5, 1, 69, 7, 39, 873, 1136, 773, 812, 7]

    接下来,让我们看看是否可以使用decode方法将这些token IDs转回文本:

    tokenizer.decode(ids)
    
    • 1

    '" It\' s the last he painted, you know," Mrs. Gisburn said with pardonable pride.'

    据上面的输出,我们可以看到decode方法成功地将token IDs转换回原始文本。

    到目前为止,一切都很好。我们实现了一个tokenizer,能够根据训练集中的片段对文本进行tokenizing和de-tokenizing。现在让我们将其应用于训练集中未包含的新文本样本:

    text = "Hello, do you like tea?"
    tokenizer.encode(text)
    
    • 1
    • 2

    问题是短篇小说中 没有使用“Hello” 这个词。因此,它不包含在词汇中。这凸显了在使用LLM时需要考虑大型且多样化的训练集来扩展词汇量。

    在下一节中,我们将在包含未知单词的文本上进一步测试tokenizer,我们还将讨论其他特殊tokens,这些tokens可用于在训练期间为 LLM 提供进一步的上下文。

    2.4 添加特殊上下文tokens (Adding special context tokens)

    在上一节中,我们实现了一个简单的 分词器(tokenizer) 并将其应用于训练集中的段落。在本节中,我们将修改此分词器以处理未知单词。

    我们还将讨论特殊上下文tokens的使用和添加,这些tokens可以增强模型对文本中上下文或其他相关信息的理解。例如,这些特殊tokens可以包括未知单词和文档边界的markers。

    特别是,我们将修改上一节中实现的词汇表和分词器 ,SimpleTokenizerV2以支持两个新tokens:<|unk|><|endoftext|>如图 2.9 所示。

    图 2.9 我们向词汇表中添加特殊tokens来处理某些上下文。例如,我们添加一个 <|unk|> tokens来表示新的和未知的单词,这些单词不属于训练数据,因此也不属于现有词汇表。此外,我们添加一个 <|endoftext|> tokens,可用于分隔两个不相关的文本源。

    如图 2.9 所示,我们可以修改分词器,使其在遇到不属于词汇表的单词时使用<|unk|>token。此外,我们在不相关的文本之间添加了一个token。例如,当在多个独立文档或书籍上训练类似 GPT 的 LLM 时,通常会在前一个文本源后面的每个文档或书籍之前插入一个token,如图 2.10 所示。这有助于LLM了解,尽管这些文本源是为了训练而串联起来的,但实际上它们是不相关的。

    图 2.10 当使用多个独立的文本源时,我们在这些文本之间添加 <|endoftext|> tokens。这些 <|endoftext|> tokens充当markers,表示特定片段的开始或结束,从而允许LLM进行更有效的处理和理解。

    现在让我们修改词汇表以包含这两个特殊tokens和<|endoftext|>,将它们添加到我们在上一节中创建的所有唯一单词的列表中:

    preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text)
    preprocessed = [item.strip() for item in preprocessed if item.strip()]
    
    all_tokens = sorted(list(set(preprocessed)))
    all_tokens.extend(["<|endoftext|>", "<|unk|>"])
    
    vocab = {token:integer for integer,token in enumerate(all_tokens)}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    len(vocab.items())
    
    • 1

    1161

    根据上面 print 语句的输出,新的词汇量为 1161(上一节中的词汇量为 1159)。

    作为额外的快速检查,让我们打印更新词汇表的最后 5 个条目:

    for i, item in enumerate(list(vocab.items())[-5:]):
        print(item)
    
    • 1
    • 2

    ('younger', 1156) ('your', 1157) ('yourself', 1158) ('<|endoftext|>', 1159) ('<|unk|>', 1160)

    根据上面的代码输出,我们可以确认这两个新的特殊tokens确实已成功合并到词汇表中。接下来,我们相应地调整代码SimpleTokenizerV1中的分词器,如SimpleTokenizerV2 所示:

    class SimpleTokenizerV2:
        def __init__(self, vocab):
            self.str_to_int = vocab
            self.int_to_str = { i:s for s,i in vocab.items()}
        
        def encode(self, text):
            preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
            preprocessed = [item.strip() for item in preprocessed if item.strip()]
            preprocessed = [item if item in self.str_to_int 
                            else "<|unk|>" for item in preprocessed]
    
            ids = [self.str_to_int[s] for s in preprocessed]
            return ids
            
        def decode(self, ids):
            text = " ".join([self.int_to_str[i] for i in ids])
            # Replace spaces before the specified punctuations
            text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
            return text
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    与我们在上一节的SimpleTokenizerV1中实现的相比,新的代码SimpleTokenizerV2用 <|unk|> tokens替换了未知单词。

    现在让我们在实践中尝试一下这个新的分词器。为此,我们将使用一个简单的文本示例,该示例由两个独立且不相关的句子连接而成:

    tokenizer = SimpleTokenizerV2(vocab)
    
    text1 = "Hello, do you like tea?"
    text2 = "In the sunlit terraces of the palace."
    
    text = " <|endoftext|> ".join((text1, text2))
    
    print(text)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Hello, do you like tea? <|endoftext|> In the sunlit terraces of the palace.

    tokenizer.encode(text)
    
    • 1

    [1160, 5, 362, 1155, 642, 1000, 10, 1159, 57, 1013, 981, 1009, 738, 1013, 1160, 7]

    在上面,我们可以看到token IDs 列表包含 <|endoftext|> 分隔符令牌 1159 以及两个用于未知单词的 1160 令牌。

    让我们对文本进行去de-tokenize以进行快速健全性检查:

    tokenizer.decode(tokenizer.encode(text))
    
    • 1

    '<|unk|>, do you like tea? <|endoftext|> In the sunlit terraces of the <|unk|>.'

    1. “Hello” 和 “palace” 都被编码为相同的 token ID —— 在这种情况下是 <|unk|> —— 那么在解码阶段实际上是无法区分这两个词的。分词器无法从同一的 <|unk|> 代号中重建原始词汇,因为它已经失去了关于哪个 <|unk|> 对应于哪个原始词汇的信息。
    2. 在大多数情况下,未知词的确切身份对于模型的后续处理并不重要,因为模型通常依赖于上下文来理解或生成文本。

    通过将上面的de-tokenized文本与原始输入文本进行比较,我们知道训练数据集(Edith Wharton的短篇小说《The Verdict》)不包含“Hello”和“palace”一词。

    到目前为止,我们已经讨论了tokenization作为处理文本作为LLM输入的一个重要步骤。根据LLM,一些研究人员还考虑其他特殊tokens,例如:

    • [BOS](序列开始):此token marks(marks,标记)文本的开始。对于LLM来说,它意味着一段内容的开始。
    • [EOS](序列结束):此token位于文本的末尾,在连接多个不相关的文本时特别有用,类似于<|endoftext|>。例如,当组合两篇不同的维基百科文章或书籍时,[EOS]token指示一篇文章的结束位置和下一篇文章的开始位置。
    • [PAD](padded):当训练批次大小大于 1 的 LLM 时,批次可能包含不同长度的文本。为了确保所有文本具有相同的长度,较短的文本将使用令牌进行扩展或“padded” [PAD],直到批次中最长文本的长度。

    请注意,用于GPT模型的分词器不需要使用上述任何提到的特殊tokens,而只使用一个简单的<|endoftext|> token 。这个<|endoftext|>类似于上面提到的 [EOS] token。此外,该<|endoftext|> token也用于padding。然而,正如我们将在后续章节中探讨的,当在批量输入上训练时,我们通常使用掩码,这意味着我们不关注填充的tokens。因此,选择用于填充的特定tokens变得不重要。

    此外,用于GPT模型的分词器也不使用任何专门的<|unk|> token来处理词汇表外的词。相反,GPT模型使用byte pair encoding tokenizer字节对编码分词器,该分词器将单词拆分为子词单元,我们将在下一节中讨论这一点。

    Build a Large Language Model (From Scratch) 从头开始构建大型语言模型(第二章)学习笔记(下)




  • 相关阅读:
    51单片机+DS1302设计一个电子钟(LCD1602显示时间)
    电子学会Python二级知识点(自己整理)
    计算机毕业设计ssm软件学院社团管理系统l62lq系统+程序+源码+lw+远程部署
    从零开始学前端:DOM、BOM、表单事件、事件捕获 --- 今天你学习了吗?(JS:Day16)
    【蓝桥杯冲击国赛计划第6天】字典
    安装、升级pip,但是python -m pip install --upgrade pip报错的解决办法
    2023-9-1-虚拟网卡学习
    包装类笔记
    特殊类与类型转换
    Android 基础知识3-1项目目录结构
  • 原文地址:https://blog.csdn.net/weixin_46460463/article/details/137874698