本文导读:本篇文章是“基于开源大模型库快速实现AI应用”的系列教程之一。在上一篇《大语言变换器模型的架构及其工作原理介绍》中,我们全面地探讨了Transformer大模型的起源、基本结构、工作原理以及其在自然语言处理等领域的应用。文章还深入分析了自注意力机制的核心原理、计算过程,以及多头自注意力的具体实现。通过详细的案例,我们解读了三种Transformer模型:纯编码器模型、纯解码器模型,以及编码器-解码器结合模型,并对它们之间的差异及应用场景进行了阐述。
在本文中,我们将从一个端到端的实例出发,展示如何直接利用模型和分词器类函数,复现文章《利用开源Transformer模型实现主要的NLP文本相关应用》中所介绍的管道(pipeline)功能。此外,你还将了解如何使用模型的各种API进行配置,这包括加载模型和处理数值输入,从而得到预测结果。我们还将深入探讨分词器的各个处理步骤和功能,并在文章末尾展示如何使用大模型进行多句子的批处理,以及相关的注意事项。
关键词:Transformer(变换器)、分词器、架构、检查点、模型。
前文回顾:AI技术干货|深度剖析transformer大模型管道功能背后的工作原理(上篇)
3. 模型
3.1. 变换器模型
在本节中,我们将详细探索如何创建和使用各种模型。在Hugging Face库中,创建模型有多种方式。其中,AutoModel类为我们提供了从预训练检查点实例化任何模型的便捷方法。当然,如果你对特定的模型有深入了解,也可以选择直接使用特定的模型类来进行更为精细的创建。
AutoModel及其相关类是对库中所有模型的高效封装,它们能够根据你的具体需求,自动选择并实例化合适的模型架构。这种设计使得AutoModel成为一个既灵活又智能的工具。但如果你已经清楚自己想要使用的模型类型,例如BERT,那么直接使用与BERT对应的类也是一个明智的选择。
以“我爱喝溧阳白茶”这句中文为例,Hugging Face为我们提供了非常方便的工具。我们可以轻松地使用库中的AutoModel类或BERT模型类,从预训练的检查点中实例化模型,然后对这句话进行深入的分析。
3.1.1. AutoModel 类
AutoModel是Hugging Face库的核心组件之一,它为用户提供了从检查点实例化任何模型的能力。这个类的优势在于,当你不确定应该选择哪种模型架构时,它可以为你提供方便。无论你正在处理的是BERT、GPT-x还是其他任何Transformer模型,AutoModel都能为你提供正确的模型实例。
例如,以下代码展示了如何使用Hugging Face的transformers库加载预训练的Transformer模型:
这段代码是使用 Hugging Face 的 transformers 库来加载一个预训练的 Transformer 模型。下面我们逐行解释这段代码的功能:
1. 导入 AutoModel 类:
这行代码从 transformers 库中导入了 AutoModel 类。AutoModel 类是一个通用类,用于从给定的检查点(checkpoint)加载模型,它会自动判断模型的类型(例如 BERT、GPT-2 等)。
2. 指定模型检查点:
这行代码定义了一个字符串变量 checkpoint,该变量存储了要加载模型的检查点的名称。这个检查点是 Hugging Face Model Hub 上的一个模型标识符,指向了一个具体的预训练模型。
3. 加载预训练模型:
这行代码使用 AutoModel.from_pretrained 方法加载了指定检查点的预训练模型,并将其实例化为一个模型对象,存储在变量 model 中。from_pretrained 方法会根据检查点自动识别模型的类型,并加载相应的模型权重和配置。
总之,这段代码的主要目的是通过指定的模型检查点,使用 AutoModel 类从 Hugging Face Model Hub 加载并实例化一个预训练的 Transformer 模型。在这个例子中,加载的是由用户 lxyuan 上传的、基于 DistilBERT 架构的、支持多语言的情感分析模型。
3.1.2. 直接使用具体的模型类
虽然 AutoModel 类非常方便,但如果你已经知道你想要使用的模型类型,你还可以选择直接定义模型架构的类。例如,如果我们知道要使用 BERT 模型,我们可以直接使用 BertModel 类来实例化模型。
这种方法的优势在于,它允许更精确地控制模型的实例化过程,因为你可以直接设置模型架构的各种参数。这对于熟悉特定模型架构的用户来说,这是一个非常有用的特性。
3.1.2.1. 创建 BERT 模型实例
要使用 BERT 模型,我们首先需要加载配置对象,然后使用这个配置对象来实例化模型。以下是一个简单的例子:
在这个例子中,我们首先导入了 BertConfig 和 BertModel 类,然后创建了一个 BertConfig 对象,并使用这个配置对象实例化了一个 BERT 模型。
配置包含用于构建模型的许多属性:
虽然你可能还没有完全了解这些属性的全部作用,但应该能够了解其中的一些:hidden_size 属性定义了 hidden_states 向量的大小,而 num_hidden_layers 定义了 Transformer 模型的层数。
3.2. 不同的加载方法
3.2.1. 使用默认配置创建并初始化模型
当我们采用默认配置创建模型时,模型的参数会被随机值初始化:
此时,虽然这个模型已经可以被调用,但由于它还未经过任何训练,所以其输出的结果将是无意义的,我们通常称之为“垃圾”输出。理论上,我们完全可以从头开始,针对特定任务对这个模型进行训练。然而,实际操作中,这通常需要消耗大量的时间、数据和计算资源,而且还会带来一定的环境负担。
例如,如果我们想要训练一个能够理解“我爱喝溧阳白茶”这类中文句子的模型,我们可能需要准备大量类似的中文文本数据,并且花费数天、数周甚至数月的时间来进行模型训练,这对于个人和小型团队来说,无疑是一项巨大的挑战。
因此,为了避免这种不必要且重复的努力,能够方便地共享和重用已经训练过的模型变得尤为重要。通过使用预训练模型,我们不仅可以大大缩短模型开发的周期,还可以利用模型在大规模数据上学到的知识,来提高模型在特定任务上的表现。
3.2.2. 加载并使用预训练模型
在实际应用中,我们通常会优先考虑使用如 from_pretrained() 方法加载预训练模型,这样可以确保我们快速获得一个性能优良且适应多种任务的模型,从而更加高效地解决实际问题。
在这个代码示例中,我们并没有使用 BertConfig,而是直接通过 bert-base-cased 这个标识符来加载了一个预训练模型。这个模型检查点是由 BERT 的原作者们所训练的,关于这个模型更多的详细信息和特性,你可以访问模型卡片来了解。
此时,该模型已经使用检查点中的所有权重进行了初始化,这意味着它已经准备好直接用于进行各种推断任务了。同时,这个模型还支持在新的任务上进行微调。通过在预训练的权重基础上进行训练,我们可以避免从零开始训练模型,从而更快地获得优秀的模型性能。
值得一提的是,模型的权重文件在第一次下载后会被缓存,因此在未来使用 from_pretrained() 方法加载模型时,系统会直接读取缓存中的文件,无需重新下载,这大大加快了模型加载的速度。默认情况下,这些缓存文件被存放在 ~/.cache/huggingface/transformers 目录下。当然,如果你有特殊需求,也可以通过设置 HF_HOME 环境变量来自定义缓存文件夹的位置。
此外,用于加载模型的标识符不仅仅是 bert-base-cased,实际上,你可以在 Model Hub 上找到众多与 BERT 架构兼容的模型,并通过相应的标识符进行加载。
举个例子,如果你在进行中文文本处理任务,你可能会选择加载 bert-base-chinese 模型,代码如下:
这样,你就成功加载了一个适用于中文任务的 BERT 模型,可以利用它进行下一步的文本分析和处理工作。
3.3. 保存模型的方法
保存模型与加载模型一样,都是非常直观且简单的。我们主要利用 save_pretrained() 方法来实现模型的保存,这个方法与之前提到的 from_pretrained() 方法相对应:
执行上述代码后,会在你的计算机磁盘上的指定目录 directory_on_my_computer 中保存两个文件:
当你打开并查看 config.json 文件时,你会发现里面记录了构建模型架构所必需的各种属性。除此之外,这个文件还包含了一些有用的元数据,例如模型检查点的来源信息,以及你在上次保存模型检查点时所使用的 Transformers 库的版本信息。
另一个文件 pytorch_model.bin 被称为状态字典(state dictionary),它存储了模型的所有权重信息。这两个文件是密不可分的,因为 config.json 文件中的配置信息是构建模型架构的基础,而 pytorch_model.bin 文件中存储的模型权重则是模型进行预测和推理的关键参数。
例如,如果你训练了一个用于文本分类的模型,并且希望在其他项目中复用这个模型,你就可以通过这种方式将模型保存下来。之后,无论是你自己还是其他人,都可以通过 from_pretrained() 方法轻松地加载并使用这个模型,实现模型的分享和复用,极大地提高了工作效率。
3.4. 使用 Transformer 模型进行推断
你已经掌握了如何加载和保存模型,接下来,我们将试着用它来做一些预测。需要注意的是,Transformer 模型只能处理数字,这些数字是由分词器生成的。但在深入了解分词器之前,我们先来看看模型需要的输入是什么。
虽然分词器可以帮我们把输入转换成合适的张量格式,但为了让你更清楚地了解背后的过程,我们会简要介绍在输入数据送入模型之前需要做的准备工作。
假设我们有以下几个文本序列:
分词器会把这些文本序列转换为通常被称为 输入 ID 的词汇表索引。这样,每个文本序列都被转换成了一个数字列表。转换的结果如下:
这是一个编码后的序列列表,也就是一个二维列表。张量要求数据是矩形形状的(可以想象成矩阵)。这个“数组”已经满足这个要求,所以我们可以轻松地将其转换为张量:
3.4.1. 将张量输入到模型中
要让模型处理这些张量,操作非常简单——只需将输入数据传入模型即可:
尽管模型可以接受多种参数,但输入 ID 是唯一必需的。稍后,我们会详细介绍其他参数的功能以及在什么情况下需要使用它们。但在此之前,我们需要更深入地了解如何使用分词器来构建 Transformer 模型可以理解的输入。
3.5. 小结
总的来说,Hugging Face 为我们提供了丰富的方法来创建和使用 Transformer 模型。无论你是选择使用通用的 AutoModel 类,还是决定直接使用某个特定的模型类,Hugging Face 都能为你提供所需的工具,帮助你轻松地实例化和使用各种 Transformer 模型。
如你所见,我们完全可以用 AutoModel 类来替代 BertModel。从此以后,我们将采用这种方式,因为这样可以使代码与特定的检查点无关;换句话说,如果你的代码适用于一个模型,那么它应该也能适用于其他模型。即使模型的架构有所不同,只要这些模型都是为了解决类似的任务(如情感分析)而训练的,这种方法都是适用的。
4. 分词器
分词器在自然语言处理(NLP)流程中占据着举足轻重的地位,它是实现文本与模型之间沟通的关键工具。简而言之,分词器的主要职责就是将人类可读的文本转换为机器学习模型可理解和处理的数值数据,因为模型本身是无法直接解读文本信息的。在这一节中,我们将深入探讨分词器在这一转换过程中都执行了哪些关键步骤?
在NLP任务中,我们通常处理的是原始文本数据,例如一句中文:“我喜欢自然语言处理”。但由于机器学习模型的限制,它们只能处理数值型数据,这就需要我们找到一种方法,将这些原始文本有效地转换为数字序列。这正是分词器的核心工作,而实现这一转换的方法有很多种,每种方法都有其独特的优势和应用场景。我们的目标是找到能够最大程度上保留原文意义的表示方法,即对模型来说,这种表示能够包含最多的信息,同时,如果可能的话,我们还希望这种表示尽可能地简洁。
为了更好地理解分词器的工作原理和方法,我们将介绍一些常见的分词算法,并通过实例来回答一些关于分词的常见问题。这将帮助我们揭开分词器的神秘面纱,理解它是如何将自然语言转化为模型可理解的数字语言的。
4.1. 基于单词的分词(Word-based Tokenization)
当我们谈论分词器时,首先浮现在脑海中的往往是基于单词的分词器。这种分词器的设置和使用通常都非常直观,只需遵循一些基本规则,就能实现文本到数值的有效转换。例如,在下图中,我们的目标是将原始文本分割成单词,并为每个单词找到一个数值表示:
图 3 分词的简单二分类
文本的分割方式有很多种,其中最直观的一种是使用空格将文本分割成单词。这可以通过Python的 split() 函数轻松实现:
输出:
此外,还有一些基于单词的分词器,它们对待标点符号有着额外的处理规则。使用这类分词器时,我们可能会得到一个庞大的“词汇表”,这个词汇表由语料库中所有独立标记的总数定义。
在这种方法中,每个单词都会被分配一个唯一的ID,范围从0到词汇表的大小。模型便是通过这些ID来识别和处理每个单词的。
然而,如果我们想要使用基于单词的分词器来全面覆盖一种语言,我们就需要为该语言中的每个单词分配一个唯一标识符,这将导致标识符数量的激增。以英语为例,英语中有超过500,000个单词,因此要建立从每个单词到唯一ID的映射,我们需要管理如此庞大的ID数量。更进一步,形态变化的单词,如“dog”和“dogs”,会被视为两个不同的标记,模型最初无法识别它们之间的相似性:它会将这两个单词视为无关联的。理,“run”和“running”这样的词形变化,最初也不会被模型认为是相似的。
为了解决词汇表中不存在的单词问题,我们通常会引入一个特殊的“未知”标记,通常表示为“[UNK]”或“”。如果你发现分词器生成了大量这样的标记,这通常意味着分词器无法为某些单词找到合适的表示,从而在信息处理过程中造成信息的丢失。制定词汇表的目标就是要尽量减少将单词分词为未知标记的情况。
为了减少未知标记的出现,一种可行的方法是使用更为细粒度的分词器,例如基于字符的分词器。这种分词器能够更好地处理语言的多样性,减少未知标记的生成,从而更有效地保留原文信息。
4.2. 基于字符的分词(Character-based Tokenization)
与基于单词的分词器不同,基于字符的分词器是将文本分割成单个字符,而非完整的单词。这种分词方法带来了两个主要的优势:
• 词汇表规模更小:由于只需表示单个字符,因此词汇表的大小大大减小。
• 未登录词更少:由于任何单词都是由字符组成的,这种方法几乎可以消除未登录词(未知标记)的出现。
然而,这种方法也面临着处理空格和标点符号的挑战:
图 4 基于字符分词
尽管基于字符的分词在某些方面具有优势,但它并非完美无缺。由于现在的表示是基于单个字符而非完整单词,每个标记所含的语义信息量相对较少。例如,在英文中,字符“a”、“e”、“i”等本身的含义远不如单词“apple”、“elephant”、“island”等丰富。然而,这种情况会因语言的不同而有所变化;例如,在中文中,每个字符往往都蕴含着丰富的意义,比如“爱”、“山”、“水”等,其信息量比拉丁语中的单个字符要丰富得多。
另外,使用基于字符的分词器时,我们会发现模型需要处理的标记数量大大增加。一个原本在基于单词的分词器中只被视为一个标记的单词,如“processing”,在基于字符的分词中会被分割成10个标记,即“p”、“r”、“o”、“c”、“e”、“s”、“s”、“i”、“n”、“g”。
为了综合利用基于单词和基于字符的分词方法的优点,研究人员提出了第三种分词技术-子词分词(Subword Tokenization)。这种方法旨在找到一种平衡,既能保留单词级别的语义信息,又能有效处理未登录词,从而在自然语言处理任务中实现更好的性能。
4.3. 子词分词(Subword Tokenization)
子词分词算法基于一个核心原则,即频繁出现的常用词应保持完整,而较为罕见的词则应分解为具有独立意义的子词或词根。
以英文单词“unbelievable”为例,它可能被视为一个较为复杂的词,可以被分解为“un-”、“believe”和“-able”。这三个部分作为独立的子词都具有明确的语义,同时,“unbelievable”的整体含义通过这些子词的组合得以完整保留。
下面是一个具体的例子,展示了子词分词算法如何对英文序列“Subword tokenization is fascinating!”进行分词:
图 5 基于子词分词
通过这种方法,子词分词为我们提供了丰富的语义信息。例如,在上述例子中,“fascinating”被分割成了“fascinat”和“ing”,这两个子词都具有独立的语义含义,同时又是空间有效的,即用较少的标记便能表示一个较长的词。这种策略使我们能够通过较小的词汇表实现对语言的广泛覆盖,减少了未知标记的出现。
子词分词方法在处理一些粘着语言,如土耳其语时,表现尤为出色。在这类语言中,人们可以通过将不同的子词或词根串联起来,形成极其复杂且长度不定的单词,而子词分词能够有效地解析这些复合词,保留其原始的语义信息。
4.4. 更多分词技术
毫无疑问,分词领域中存在着众多先进的技术和方法。下面,我们将简要介绍几种在自然语言处理模型中广泛应用的分词技术:
· Byte-level BPE(字节级字节对编码):
o 应用实例:GPT-2模型中采用了这种分词技术。
o 特点:Byte-level BPE通过将文本分解为字节级的符号,实现了对任意语言的无损分词,极大地提高了模型的通用性和灵活性。
o 示例:例如,单词“encoding”可能被分解为“en”、“co”和“ding”。
· WordPiece:
o 应用实例:BERT模型中使用了WordPiece分词技术。
o 特点:WordPiece通过将单词分解为子词或字符,有效地减小了词汇表的大小,同时保留了丰富的语义信息。
o 示例:例如,单词“language”可能被分解为“lan”和“guage”。
· SentencePiece或Unigram:
o 应用实例:多种多语言模型,如mBERT和XLM,采用了SentencePiece或Unigram分词方法。
o 特点:这些方法不依赖于预定义的空格或标点符号,能够处理多种语言和符号,适用于多语言环境。
o 示例:例如,中文句子“自然语言处理”可以被分解为“自然”、“语言”和“处理”。
通过上述的学习,你对分词器的运作机制和相关技巧应该有了深入的认识。接下来,你可以尝试使用相关的API,将这些知识运用到实际的自然语言处理项目中。
4.5. 分词器加载
加载分词器是自然语言处理中的一个重要步骤,它与加载模型一样简单且直观。分词器的加载主要基于from_pretrained()方法。此方法不仅加载分词器使用的算法(这类似于模型的架构),还加载了分词器的词汇表(相当于模型的权重)。
例如,如果我们想要加载与BERT模型使用相同检查点训练的BERT分词器,我们可以使用BertTokenizer类,如下所示:
此外,AutoTokenizer类提供了更为通用的解决方案,它将根据检查点名称自动获取库中的适当分词器类,并可以直接与任何检查点一起使用:
加载完成后,我们就可以开始使用分词器来处理文本数据了:
输出结果为:
在这个例子中,input_ids、token_type_ids和attention_mask是分词器输出的主要组成部分,我们将在后续章节中进一步探讨它们的作用和意义。
4.6. 分词器保存
在对文本数据进行处理后,我们可能需要保存经过训练或调整的分词器,以便于未来的使用和分享。保存分词器的方法与保存模型类似,主要使用save_pretrained()方法,并指定保存的目录:
通过这种方式,我们可以轻松地将分词器保存到本地计算机上的指定目录中,以便于未来的加载和使用。
责任编辑:房家辉
24小时热文
流 • 视界
专栏文章更多
- [常话短说] 【解密】全国广电工程公司 2025-03-26
- [常话短说] 【解局】广电700M,迎高光时刻! 2025-03-25
- [常话短说] 【解局】广电上市公司财报分析! 2025-03-21
- [常话短说] 【解局】广电5G有个重要推动! 2025-03-19
- [常话短说] 【解局】某上市广电网又成立新公司,干啥?! 2025-03-14