一行代码深入了解如何将成千上万个可用的ai解决方案引入到您的脚本中,利用🤗 transformers库的强大功能。
文章目录
照片由simon kadula在unsplash上提供
引言
人们以各种形式和方式使用的人类语言可以产生大量的信息,但以非结构化的方式存在。人们天性中的沟通和表达观点的需求,尤其是在当今所有可用的渠道中,导致了大量未经处理的数据,迄今为止,这些数据在业务中被最小或不被利用。
然而,近年来发生了显著变化。
人工智能(ai)领域的快速发展,特别是在自然语言处理(nlp)领域,使我们能够以编程方式理解和与这些信息进行交互,促使许多企业重新审视这些知识源,将其作为新产品的燃料。
这种紧迫感是由chatgpt的发布所引起的,它向世界展示了transformer模型的有效性,并普及了大型语言模型(llm)领域。
这个产品的简单性和通用性使每个人都可以使用这些ai过程来执行nlp领域的各种任务,而无需理解复杂的数学方程或学习如何训练和维护机器学习模型。只需打开一个聊天机器人(或调用一个api),用您的母语编写一个适当的提示,然后神奇地拥有了一个ai产品。
然而,就像所有伟大的产品一样,这个产品也是有代价的。在某些工具中,这个代价可能以订阅的形式存在,或者通常根据工具的使用情况收费,以每个单词/标记使用的费率计算。
虽然每个标记的费率在大多数情况下可能看起来非常小(1k个标记能做什么?)[1],但想象一下使用这个工具从一本有数百页的书中提取信息;如果企业不正确理解和监控,成本可能会在几秒钟内飙升并反咬回来。
所以这一切太美好而不真实吗?未必如此。
如果我告诉您,您可以找到并使用这些强大的预训练模型,而且只需使用几行代码就可以免费使用?
再次强调,不需要具体的统计数据或数据科学知识。只需要一些基本的python理解和对一些nlp任务的高级理解。如果您具备这些最低要求,hugging face和开源社区将为您提供所需的其余工具,以开放的llms形式。
什么是hugging face?
hugging face是一个开源的数据科学社区,通过其平台使其成员能够自由使用、开发和托管社区提供的预训练模型和数据集。
它的主要重点是分发和开发开放的llms,这些模型经过预训练,准备好用于各种任务,几乎不需要最终用户的干预。
因此,从研究人员和llm爱好者到微软、meta和openai等大公司的成员,都可以找到一个地方来公开发布他们的模型,并寻找适合他们不断增长的需求的新模型。
那么,为什么不亲自看看这些模型呢?
模型中心
要找到您需要的模型,您只需浏览hugging face网站并导航到模型中心。在那里,任何人都可以找到超过480,000个经过预训练的模型,经过微调以执行特定任务。这些任务不限于特定领域,您可以找到训练用于处理文本、音频、计算机视觉任务等的模型,以及更专门为每个领域的特定子任务进行了更专门化的模型。

hugging face模型界面:来自https://huggingface.co/models的屏幕截图
数据集中心
此外,如果您仍然找不到完全适合您用例的现成模型,您可以选择使用包含标记数据的各种数据集之一对预训练模型进行微调(重新训练其中的一小部分)。您可以再次在hugging face网站上找到这些数据集,但这次是在数据集中心中。这些数据集涵盖不同领域,并旨在为模型中心中呈现的大多数任务或子任务提供数据。

hugging face数据集界面:来自https://huggingface.co/datasets的屏幕截图
但是,如果您没有使用这些模型的方法,您可以做些什么呢?
出于这个原因,hugging face创建了一个非常直观的高级python库,名为🤗 transformers。该库允许每个人都可以使用这些模型,甚至在他们的本地机器上使用它的最基本对象——**pipeline()**函数。这个函数可以将用于端到端nlp过程的所有必要步骤包装成一行代码,如下一节中所示。
transformers pipeline()函数
在这里,我们将深入研究🤗 transformer库中最强大的函数之一:pipeline()函数。
这是一个用户友好的api,它在复杂的transformer库代码之上提供了一个抽象层,通过提供特定的pipeline名称或模型,简化了各种nlp任务的推理过程。正如其名称所示,它是多个需要按顺序执行的步骤的包装器,用户可以输入原始文本并使用模型从中获取推理结果。[2]
这些必要的步骤可以分为以下三个类别:
- 对接收到的原始文本进行预处理,使其适合模型
将用户提供的文本转换为模型可以理解的数值表示,这个过程被称为编码,与用于训练的模型的形式和方式相同。 - 使用您选择的模型进行推理
在本地机器上下载一个预训练模型,该模型已准备好用于所选任务,该模型以预处理的文本作为输入,并根据所选任务预测输出(例如文本分类、文本生成)。 - 对结果进行后处理,使其可读
通过将模型的非标准化结果转换为所选任务的预期输出,通常是将生成的标记转换回可读文本,或将模型的逻辑规范化为分类任务中每个类别的概率(例如情感分析),来理解模型的输出。
要使用这个pipeline函数,您首先需要安装transformer库以及用于创建模型的深度学习库(主要是pytorch、tensorflow或jax),只需在终端中使用pip install命令即可。然后您就可以开始了。(为此,我们假设您的计算机上已安装了python 3.10或更高版本。否则,您可以从这里安装。)
# 要运行大多数可用模型的推理
pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0
pip install jax==0.4.23
# 本文只需要这些库
pip install tensorflow==2.15.0
pip install transformers==4.35.2
# 对于后处理步骤,您还需要
pip install numpy==1.23.5
如果您认为这很容易,那么等着看如何使用它来建立一个端到端的流水线,以查找每个给定文本中的情感。
# 导入pipeline函数
from transformers import pipeline
# 使用仅任务的文本分类器进行情感分析进行初始化
classifier = pipeline(task='sentiment-analysis')
您已经完成了所有工作。只需这两行代码,您就创建了一个包含一系列步骤的流水线,可用于执行所需的任务,包括用于该任务的完全训练和微调的模型。
然后,唯一剩下的事情就是使用它进行推理。
# 一行代码从模型中进行推理
single_result = classifier('this movie is awesome')
print(single_result)
# 或多个句子
multiple_results = classifier(["this movie is awesome", "this movie is awful"])
print(multiple_results)
# 结果
# >>> [{'label': 'positive', 'score': 0.9998761415481567}]
# >>> [{'label': 'positive', 'score': 0.9998761415481567}, {'label': 'negative', 'score': 0.9998006224632263}]
这很酷,对吧?
我们只需要一行代码就可以得到一个准备好的解决方案,而不是花费漫长的夜晚起草和调试冗长的脚本,并从头开始训练和微调不同的模型(我在大学时的过去自己流下了眼泪)。
不需要广泛的统计学或机器学习知识。您甚至不需要知道存在哪些模型。只需指定您想要完成的任务,然后让流水线确定最佳模型和所需工具。
然而,如果您不想停留在默认模型和设置上,而是更充分地利用hugging face社区中开发和共享的开放llms的所有可用功能,让我们看看您可以在这一行代码中调整哪些其他内容。
pipeline()函数的关键特性
与其他所有对象一样,这个对象也有一些额外的参数,可以通过提供适当的参数来进一步定制其功能。其中一些重要的参数如下:
task(字符串参数)
在官方文档中列出了可用于自然语言处理任务的任务名称,如下所示:
"question-answering",将返回一个questionansweringpipeline。"summarization",将返回一个summarizationpipeline。"table-question-answering",将返回一个tablequestionansweringpipeline。"text2text-generation",将返回一个text2textgenerationpipeline。"text-classification"(或使用别名"sentiment-analysis"),将返回一个textclassificationpipeline。"text-generation",将返回一个textgenerationpipeline。"token-classification"(或使用别名"ner"),将返回一个tokenclassificationpipeline。"translation",将返回一个translationpipeline。"translation_xx_to_yy",将返回一个translationpipeline。"zero-shot-classification",将返回一个zeroshotclassificationpipeline。
如果只指定了此参数,该对象将通过从模型库中选择和加载默认模型来初始化一个端到端的流水线,以及相应的分词器来执行预处理和(在某些情况下)后处理步骤。
除了这些任务外,我相信这些任务已经足够让您开始使用自然语言处理领域了。还有许多其他领域的任务,例如计算机视觉、音频和多模态任务,您可以在文档中探索。有关支持的任务的更多信息,请参阅此处。
model(字符串参数)
在此参数中,您可以指定流水线将用于进行预测的模型。这可以与任务参数一起使用,替换默认模型,或者您可以只指定一个针对特定用途进行了微调的模型。要在流水线中指示模型,您需要提供一个checkpoint字符串,它是模型的唯一标识符。可以在hugging face模型库上找到此标识符,或者通过提供自己预训练模型的路径来获取。
例如,模型库中的一个检查点可能如下所示:‘'nlptown/bert-base-multilingual-uncased-sentiment’,这是一个针对情感分析进行了微调的基于bert的多语言模型。
tokenizer(字符串参数)
与模型参数一样,您还可以指定要在流水线的预处理和后处理部分中使用的分词器。在这里,您可以再次使用模型检查点字符串作为参数,或者使用自己预训练的分词器。如果您想将分词器微调到特定的数据集上,这一点非常重要。
device(整数参数)
此参数允许您定义流水线将分配到的处理器。它接受整数参数,从**-1开始,将cpu设置为主要资源,值≥ 0将在与提供的cuda设备id关联的gpu**上运行模型。在这里,使用gpu是机器学习和llm模型的标准硬件选择,因为它们针对内存分配和并行性进行了优化。这可以显著加快模型的推理速度。
这些是您可以在第一个流水线中轻松使用的一些重要参数。但是,您可以在此处找到所有可用参数。
通过这些附加参数,您可以灵活地定制默认流水线,并创建一个与您的特定要求和数据集完全对齐的定制流水线。对于那些对进一步优化此过程感兴趣的人,让我们深入了解一下这个流水线在幕后是如何工作的。
流水线的内部实现
在这里,我们将解释流水线函数的每个步骤中发生的情况,并通过单独运行每个步骤来复制此一行神奇代码的结果。
这是一个不必要阅读的部分,因为在大多数情况下,一个合适的流水线或模型就足以获得非常好的结果。然而,我认为了解这个函数的工作原理以进一步修改和定制它以满足您的特定需求是很重要的。
因此,正如我们在前一节中所解释的,流水线函数在每次调用时执行以下步骤[3]:
- 对原始文本进行预处理
- 使用模型进行推理
- 对模型的输出进行后处理
让我们更详细地研究每一个步骤。在本例中,我们将使用tensorflow深度学习库进行处理。使用pytorch或jax可能会有一些小的变化。
预处理:原始文本的分词
尽管这些模型被称为语言模型,但实际上它们无法处理原始文本形式的输入。预期的输入可以根据模型的不同而有所不同,但在所有情况下,它将是数字形式的。这个过程称为分词,由一个分词器对象执行以下步骤:
- 对文本输入进行规范化,去除任何格式。
- 将输入文本拆分为单词、子词或称为标记的符号。
- 如果模型需要,添加文本中的特殊标记。
- 将每个标记映射到模型在训练期间使用的词汇表中的唯一id。
- 将这个id列表转换为模型可以接受的张量(类似于numpy数组)。
这些步骤在下面的图像中可以找到:

分词步骤:作者提供的图像,灵感来自https://huggingface.co/learn/nlp-course/chapter6/4
正如您所看到的,这个过程中的许多步骤都依赖于所使用的模型。原因是为了使模型能够从某些数据中进行推理,它需要以与训练时相同的形式接收数据。
因此,为了获得最佳的推理结果,您需要下载与模型使用的相同步骤和词汇表的分词器类。为此,我们导入autotokenizer类,通过提供其检查点在**from_pretrained()**方法中下载和缓存与模型相关联的分词器的配置和词汇表,如下所示。
from transformers import autotokenizer
# 指定hugging face模型库的模型检查点
model_checkpoint = 'distilbert-base-uncased-finetuned-sst-2-english'
# 通过提供检查点来演示分词算法和分词器的词汇表
tokenizer = autotokenizer.from_pretrained(model_checkpoint)
这将初始化分词器,然后您可以提供原始文本,然后 - 您猜对了 - 在一行代码中,您就可以获得其分词表示形式,供模型使用。
# 一种方式 - 您可以一行代码获得结果
model_inputs_one = tokenizer('this movie is awesome', return_tensors="tf")
print(model_inputs_one)
# 结果
# >>> {'input_ids': <tf.tensor: shape=(1, 6), dtype=int32, numpy=array([[ 101, 2023, 3185, 2003, 12476, 102]], dtype=int32)>, 'attention_mask': <tf.tensor: shape=(1, 6), dtype=int32, numpy=array([[1, 1, 1, 1, 1, 1]], dtype=int32)>}
然而,在这里,我们还可以将这行代码分解为其特定的任务。
正如我们在本节开头所说,分词器的前两个任务是通过删除任何格式化并将其拆分为单个标记的方法来规范化文本。为了实现这一点,我们将文本字符串传递给分词器的**tokenize()**方法。
# 将字符串拆分为标记
tokens = tokenizer.tokenize('this movie is awesome')
print(tokens)
# 结果
# >>> ['this', 'movie', 'is', 'awesome']
这将返回一个标记列表,这些标记可以是单词、子词、标点符号和符号。然后,它使用**convert_tokens_to_ids()**方法将这些标记转换为数字,通过将每个标记分配给其在分词器的词汇表中对应的id来实现。这个词汇表与模型使用的词汇表相同。
# 使用分词器的字典将标记转换为id
input_ids = tokenizer.convert_tokens_to_ids(tokens)
print(input_ids)
# 结果
# >>> [2023, 3185, 2003, 12476]
所以现在我们几乎完成了;我们有了我们的数字,这很棒,但是一些模型需要一些特殊的标记来操作,例如,指定句子的开始和结束等。
您可以使用分词器的all_special_tokens属性查看每个模型所需的这些标记,以及使用all_special_ids属性查看它们在词汇表中的id。
# 查找模型所需的特殊标记
print('special tokens map:', tokenizer.all_special_tokens)
print('special tokens ids:', tokenizer.all_special_ids)
# 结果
# >>> special tokens map: ['[unk]', '[sep]', '[pad]', '[cls]', '[mask]']
# >>> special tokens ids: [100, 102, 0, 101, 103]
为了将这些标记包含在我们的标记列表中,并以模型可以处理的方式格式化输入,我们使用`prepare_for_model()`方法。这将创建一个包含模型的数值输入的字典,以及一个注意力掩码,用于指示模型在推理过程中应考虑哪些标记;将它们标记为1。
```python
# 添加模型所需的特殊标记
final_inputs = tokenizer.prepare_for_model(input_ids)
# 结果
# >>> {'input_ids': [101, 2023, 3185, 2003, 12476, 102], 'attention_mask': [1, 1, 1, 1, 1, 1]}
最后,现在我们有了我们的id列表和这个注意力掩码,我们需要将它转换为深度学习模型可以接受的张量。这可以使用tensorflow的constant()方法来完成。(或者你也可以使用pytorch。)
# 将输入id转换为张量以添加到模型中
model_inputs = tf.constant([final_inputs['input_ids']])
# 结果
# >>> tf.tensor([[ 101 2023 3185 2003 12476 102]], shape=(1, 6), dtype=int32)
正如你所看到的,我们成功地复制了直接通过将原始文本传递给分词器得到的相同输出,但采用了更详细的方法。
现在,我们准备好下载并使用我们的模型对这些数据进行推理了。
用于推理和预测的模型
这一部分在每个数据科学项目中通常是最具挑战性的部分。在各种迭代和资源投入之后,知识渊博的数据科学家需要创建、训练和验证一个特定用例的模型。
幸运的是,如今这些步骤已经从创建全新的模型转变为只需选择和实现一个预训练模型即可使用。为此,我们需要加载模型的架构以及其预训练的参数权重。
然而,这里需要理解的重点是这些模型有两个不同的部分——模型的主体或基础和它的头部。[2]
- llm模型的主体或基础是出现在变压器架构中的一些隐藏层,这些隐藏层专门用于理解自然语言并将其及其上下文转化为机器可读的格式。这些模型的输出是表示文本上下文理解的高维向量。
尽管这种对文本的理解对于nlp过程很重要,但它本身并不能解决任何特定的nlp任务。
- 要解决这些不同的任务,我们需要向模型添加一个称为头部的层。这个头部在模型的末尾添加了一些额外的层,将这个高维向量作为输入,并将其投影到适合每个任务或用例的不同维度中。
下面是一个完整的开放式llm模型的典型表示。

llm模型架构:灵感来自https://huggingface.co/learn/nlp-course/chapter2/2
使用基础模型
要加载和初始化一个没有头部的基础预训练模型,我们可以按照与分词器相同的过程提供模型的检查点给from_pretrained()方法。现在唯一的区别是,不再使用autotokenizer,而是使用transformers库中的tfautomodel类(我们在前面加上tf是因为我们在tensorflow库之上使用transformer库)。这将在初始化实例时下载和缓存模型的配置和权重。
然后,要使用基础模型,我们只需提供前一节中的预处理张量并打印结果。
# 导入tfautomodel以加载基础模型
from transformers import tfautomodel
# 提供检查点以指示模型架构和预训练权重
base_model = tfautomodel.from_pretrained('distilbert-base-uncased-finetuned-sst-2-english')
# 创建一个具有文本上下文理解的高维向量
model_results = base_model(model_inputs)
print('模型结果:',model_results)
# 结果
# >>> 模型结果: tfbasemodeloutput(last_hidden_state=<tf.tensor: shape=(1, 6, 768), dtype=float32, numpy=
# array([[[ 0.7164141, 0.13687831, 0.18230182, ..., 0.42986768,
# 0.9575808, -0.5615794 ],
# [ 0.82266235, 0.11453596, 0.09670142, ..., 0.33378035,
# 1.0659671, -0.44736785],
# ...
# [ 1.1498686, 0.18695728, 0.77936405, ..., 0.5333732,
# 0.7962912 , -0.9428895 ]]], dtype=float32)>, hidden_states=none, attentions=none)
正如你从结果的形状可以看到的,输出是一个高维向量,每一行包含输入中的每个标记的一个向量。
如果我们想将其作为输入提供给另一个更传统的机器学习或深度学习模型,这个结果可能非常有用,但它本身不能帮助我们回答当前的问题。
“这个句子是积极的还是消极的?”
为了回答这个问题,我们需要使用这种对语言的理解,并找出每个选项成为真实的可能性有多大。这是一个典型的序列数据分类任务,为了解决这个问题,我们需要向我们的模型添加适当的头部,将这个向量转换为两个分数。
使用带有头部的完整模型
因此,为了做到这一点,你需要使用tfautomodelforsequenceclassification类来初始化模型,而不是使用tfautomodel类。这个forsequenceclassification部分将下载并初始化你提供给from_pretrained()方法的模型检查点,并将其与用于序列分类的头部连接起来。
不同的任务可以使用不同的头部,你可以通过从transformer库中导入适当的类并提供任务的微调模型检查点来加载它们。(例如,使用tfautomodel**forquestionanswering**加载带有问答头部的模型)
因此,如下所示,我们以与之前相同的方式下载和初始化模型实例。我们将模型的检查点提供给from_pretrained()方法,这样我们就可以下载和缓存序列分类模型。然后我们再次传递预处理的输入,让模型发挥其魔力。
# 导入tfautomodelforsequenceclassification以加载带有用于序列分类的微调头部的模型
from transformers import tfautomodelforsequenceclassification
# 提供检查点以指示模型架构和预训练权重
model = tfautomodelforsequenceclassification.from_pretrained('distilbert-base-uncased-finetuned-sst-2-english')
# 使用模型进行推理
model_results = model(model_inputs)
print('模型结果:',model_results)
# 获取模型输出的逻辑回归
outputed_logits = model_results.logits
print('输出的逻辑回归:', outputed_logits)
# 结果
# >>> 模型结果: tfsequenceclassifieroutput(loss=none, logits=<tf.tensor: shape=(1, 2), dtype=float32, numpy=array([[-4.3079815, 4.6880302]], dtype=float32)>, hidden_states=none, attentions=none)
# >>> 输出的逻辑回归: tf.tensor([[-4.3079815 4.6880302]], shape=(1, 2), dtype=float32)
正如我们现在所看到的,我们得到了一个更易于处理的响应。这又以张量的形式呈现,其中包含各种信息,其中最重要的是逻辑回归。这些逻辑回归是模型头部输出的原始、未归一化的分数,对应于我们要分类的每个类别。因此,我们对答案“这个句子是积极的”和“这个句子是消极的”分别有一个分数。然而,对我来说,这些信息并不能提供一个易于理解的答案。
为了理解这些逻辑回归,我们需要对这些结果进行后处理,使其更符合人类的理解。
后处理
在这一部分,后处理步骤可能会根据任务的不同而有所不同。
在我们的情况下,如果模型执行的是分类任务,如序列(也适用于标记分类),则结果将是每个所检查类别的逻辑回归,即未归一化的分数。
为了将这些值归一化并将其转换为每个类别对应的概率,我们可以使用tensorflow库中的softmax函数。
# 将这些逻辑回归转换为概率
prediction = tf.nn.softmax(outputed_logits)
# 结果
# >>> tf.tensor([[1.2388763e-04 9.9987614e-01]], shape=(1, 2), dtype=float32)
然后,你可以使用模型类的这个方法找到这些概率对应的类别。这可以在模型的配置中使用id2label属性找到。
# 找到标签位置以查看对应于概率的标签
print(model.config.id2label)
# 结果
# >>> {0: 'negative', 1: 'positive'}
现在,我们有了每个类别的概率以及这些类别的位置,我们可以找到给定文本的预测情感。为此,我们可以找到模型得出的最大概率,然后找到对应于该概率的类别。
# 导入numpy函数以执行一些后处理步骤
import numpy as np
# 找到最大概率
score = max(prediction.numpy()[0])
print(score)
# 找到最大概率的位置
label_position = np.argmax(prediction.numpy()[0])
print(label_position)
# 找到对应于最大概率的标签
label = model.config.id2label[label_position]
print(label)
# 结果
# >>> 0.99987614
# >>> 1
# >>> 'positive'
有了这些信息,我们可以重构该流水线的输出,以满足从一行流水线函数派生的格式。
# 重构输出格式
output = [{'label': label, 'score': score}]
print(output)
# 结果
# >>> [{'label': 'positive', 'score': 0.99987614}]
这些是分类模型的一些后处理步骤。不同的 nlp 任务可能会采用不同的步骤。例如,对于输出文本的模型,比如问答、文本生成和摘要,模型通常会返回代表生成文本单词的标记。在这种情况下,主要的后处理步骤是将这些标记转换回可读的文本,这个过程称为解码,由分词器再次执行。
# 如果输入是文本生成模型的输出
hypothetical_outputs = final_inputs['input_ids']
print(hypothetical_outputs)
# 解码模型生成的标记
print(tokenizer.decode(hypothetical_outputs))
# 结果
# >>> [2023, 3185, 2003, 12476]
# >>> 'this movie is awesome'
我知道这有点技术性,但仍然不是那么复杂。然而,正如你所看到的,你可以使用一行管道函数或稍微更长一些、因此更易修改的代码来得出相同的结果。你可以在我的 github 仓库中找到完整示例的代码这里。
结论
总之,在各种人工智能解决方案引起轰动的当下,许多人将渴望尝试利用它们。因此,我希望本文能帮助你了解,这些解决方案不仅可以在 chatgpt api 或界面之外找到,而且不仅仅是少数人,而是每个具有一点 python 知识并使用开放的 llms 的人。
随着这一领域社区的不断壮大,将会分享更多更多的模型,并根据不同的需求和任务进行定制。这些模型在某些情况下已经超越了强大的 chat gpt,免费提供且使用成本为零。
最棒的是,你离利用它们构建你的第一个人工智能解决方案只有一行代码的距离,如果你想深入挖掘并调整以获得更好的结果,再多写几行代码也可以。这些解决方案将继续不断发展,不仅限于文本输入,还包括与和理解其他形式的非结构化数据,如图像、音频和视频交互。
📖 参考资料:
- https://openai.com/pricing
- https://huggingface.co/docs/transformers/main_classes/pipelines
- https://huggingface.co/learn/nlp-course/chapter2/2
- https://huggingface.co/learn/nlp-course/chapter1/1
发表评论