00209 NLP Course - Behind the pipeline ubuntu
前言
让我们从一个完整的例子开始,回顾一下我们在第一章执行以下代码时幕后发生了什么:
1 | from transformers import pipeline |
并得到了以下结果:
1 | [{'label': 'POSITIVE', 'score': 0.9598047137260437}, |
正如我们在第1章看到的,这个管道将三个步骤组合在一起:预处理、将输入通过模型传递,以及后处理:
让我们快速回顾一下这些步骤。
src link: https://huggingface.co/learn/nlp-course/chapter2/2
Operating System: Ubuntu 22.04.4 LTS
参考文档
使用分词器的预处理
与其他神经网络一样,Transformer模型不能直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型能够理解的数字。为此,我们使用一个分词器,它将负责:
- 将输入分解为称为标记的单词、子词或符号(如标点符号)
- 将每个标记映射到一个整数
- 添加可能对模型有用的额外输入
所有这些预处理需要以与模型预训练时完全相同的方式进行,因此我们首先需要从Model Hub下载这些信息。为此,我们使用AutoTokenizer类及其from_pretrained()方法。使用我们模型的检查点名称,它将自动获取与模型分词器关联的数据并将其缓存(所以只有在第一次运行下面的代码时才需要下载)。
由于情感分析管道的默认检查点是distilbert-base-uncased-finetuned-sst-2-english(您可以在其模型卡中看到),我们运行以下代码:
1 | from transformers import AutoTokenizer |
一旦我们有了分词器,我们可以直接将我们的句子传递给它,然后我们会得到一个准备好喂给模型的字典!剩下的唯一事情就是将输入ID列表转换为张量。
您可以使用🤗 Transformers,而不用担心后端使用的是哪个机器学习框架;可能是PyTorch或TensorFlow,或者对于一些模型来说是Flax。然而,Transformer模型只接受张量作为输入。如果您是第一次听说张量,可以将其视为NumPy数组。NumPy数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。它实际上是一个张量;其他机器学习框架的张量行为类似,并且通常与NumPy数组一样简单实例化。
为了指定我们想要返回的张量类型(PyTorch、TensorFlow或普通NumPy),我们使用return_tensors参数:
1 | raw_inputs = [ |
先不用担心填充和截断;我们稍后会解释这些。这里需要记住的主要是,您可以传递一个句子或一个句子列表,以及指定您想要返回的张量类型(如果没有传递类型,您将得到一个列表的列表作为结果)。
以下是结果作为PyTorch张量的样子:
1 | { |
输出本身是一个包含两个键的字典,input_ids和attention_mask。input_ids包含两行整数(每句一个),是每个句子中标记的唯一标识符。我们将在本章后面解释attention_mask是什么。
通过模型传递
我们可以用与分词器相同的方式下载我们的预训练模型。🤗 Transformers提供了一个AutoModel类,它也有一个from_pretrained()方法:
1 | from transformers import AutoModel |
在这个代码片段中,我们下载了我们在管道中之前使用的相同检查点(它实际上应该已经被缓存了),并用它实例化了一个模型。
这个架构只包含基本的Transformer模块:给定一些输入,它输出我们所说的隐藏状态,也称为特征。对于每个模型输入,我们将检索一个高维向量,代表Transformer模型对该输入的上下文理解。
如果您现在还不理解这些,不用担心。我们稍后会解释一切。
虽然这些隐藏状态本身可能很有用,但它们通常是模型另一部分,称为头部的输入。在第1章中,不同的任务可以使用相同的架构来完成,但每个任务都会有一个与之不同的头部。
高维向量?
Transformer模块输出的向量通常是大的。它通常有三个维度:
- 批量大小:一次处理的序列数量(在我们的例子中是2)。
- 序列长度:序列的数值表示的长度(在我们的例子中是16)。
- 隐藏大小:每个模型输入的向量维度。
它被称为“高维”是因为最后一个值。隐藏大小可以非常大(对于小型模型来说,768是常见的,而在大型模型中,这个值可以达到3072或更多)。
如果我们把我们预处理的输入喂给我们的模型,我们可以看到这一点:
1 | outputs = model(**inputs) |
1 | torch.Size([2, 16, 768]) |
注意,🤗 Transformers模型的输出行为类似于命名元组或字典。您可以通过属性(就像我们做的那样)或通过键(outputs[“last_hidden_state”])访问元素,如果您确切知道您要找的东西在哪里,甚至可以通过索引(outputs[0])访问。
模型头部:从数字中获取意义
模型头部将隐藏状态的高维向量作为输入,并将它们投影到不同的维度上。它们通常由一个或几个线性层组成:
Transformer模型的输出直接发送到模型头部进行处理。
在这个图中,模型由其嵌入层和随后的层表示。嵌入层将分词输入中的每个输入ID转换为一个向量,该向量代表关联的标记。随后的层使用注意力机制操纵这些向量,以产生句子的最终表示。
在🤗 Transformers中,有许多不同的架构可供选择,每一种都是围绕解决特定任务而设计的。以下是一个非详尽列表:
- *Model(检索隐藏状态)
- *ForCausalLM
- *ForMaskedLM
- *ForMultipleChoice
- *ForQuestionAnswering
- *ForSequenceClassification
- *ForTokenClassification
- 以及其他🤗
对于我们的例子,我们需要一个带有序列分类头部的模型(能够将句子分类为正面或负面)。所以,我们实际上不会使用AutoModel类,而是使用AutoModelForSequenceClassification:
1 | from transformers import AutoModelForSequenceClassification |
现在,如果我们看一下我们的输出的形状,维度将会低得多:模型头部将我们之前看到的高维向量作为输入,并输出包含两个值(每个标签一个)的向量:
1 | print(outputs.logits.shape) |
1 | torch.Size([2, 2]) |
因为我们只有两个句子和两个标签,所以我们的模型得到的结果的形状是2 x 2。
后处理输出结果
我们从模型得到的输出值本身不一定有意义。让我们来看一看:
1 | print(outputs.logits) |
1 | tensor([[-1.5607, 1.6123], |
我们的模型预测第一句话为[-1.5607,1.6123],第二句话为[4.1692,-3.3464]。这些不是概率,而是logits,即模型最后一层输出的原始、非归一化分数。要转换为概率,它们需要通过SoftMax层(所有🤗Transformers模型输出logits,因为用于训练的损失函数通常会将最后一个激活函数(如SoftMax)与实际损失函数(如交叉熵)融合:
1 | import torch |
1 | tensor([[4.0195e-02, 9.5980e-01], |
现在我们可以看到,模型预测第一个句子的输出为[0.0402, 0.9598],第二个句子的输出为[0.9995, 0.0005]。这些是可识别的概率分数。
要获取与每个位置对应的标签,我们可以检查模型配置的id2label属性(更多内容将在下一节介绍):
1 | model.config.id2label |
1 | {0: 'NEGATIVE', 1: 'POSITIVE'} |
现在我们可以得出结论,模型的预测如下:
- First sentence: NEGATIVE: 0.0402, POSITIVE: 0.9598
- Second sentence: NEGATIVE: 0.9995, POSITIVE: 0.0005
我们已经成功复现了管道的三个步骤:使用分词器的预处理,将输入通过模型传递,以及后处理!现在,让我们花些时间深入探讨这些步骤的每一个细节。
结语
第二百零九篇博文写完,开心!!!!
今天,也是充满希望的一天。