LLM 课程文档

模型

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

模型

Ask a Question Open In Colab Open In Studio Lab

在本节中,我们将仔细研究模型的创建和使用。我们将使用 AutoModel 类,它在您想要从检查点实例化任何模型时非常方便。

创建 Transformer

我们首先检查实例化 AutoModel 时会发生什么

from transformers import AutoModel

model = AutoModel.from_pretrained("bert-base-cased")

与分词器类似,from_pretrained() 方法将从 Hugging Face Hub 下载并缓存模型数据。如前所述,检查点名称对应于特定的模型架构和权重,在本例中是一个具有基本架构(12 层,768 隐藏层大小,12 个注意力头)和带大小写输入的 BERT 模型(这意味着大写/小写区分很重要)。Hub 上有许多可用的检查点 — 您可以在此处探索它们。

AutoModel 类及其关联类实际上是简单的包装器,旨在为给定的检查点获取适当的模型架构。它是一个“自动”类,意味着它将为您猜测适当的模型架构并实例化正确的模型类。但是,如果您知道要使用的模型类型,可以直接使用定义其架构的类

from transformers import BertModel

model = BertModel.from_pretrained("bert-base-cased")

加载和保存

保存模型就像保存分词器一样简单。实际上,模型也具有相同的 save_pretrained() 方法,该方法用于保存模型的权重和架构配置

model.save_pretrained("directory_on_my_computer")

这会将两个文件保存到您的磁盘

ls directory_on_my_computer

config.json pytorch_model.bin

如果您查看 *config.json* 文件,您将看到构建模型架构所需的所有必要属性。此文件还包含一些元数据,例如检查点的来源以及您上次保存检查点时使用的 🤗 Transformers 版本。

*pytorch_model.bin* 文件称为状态字典;它包含您模型的所有权重。这两个文件协同工作:需要配置文件来了解模型架构,而模型权重是模型的参数。

要重用保存的模型,请再次使用 from_pretrained() 方法

from transformers import AutoModel

model = AutoModel.from_pretrained("directory_on_my_computer")

🤗 Transformers 库的一个很棒的功能是能够轻松地与社区共享模型和分词器。为此,请确保您在 Hugging Face 上拥有一个帐户。如果您使用笔记本,可以轻松地登录

from huggingface_hub import notebook_login

notebook_login()

否则,在您的终端中运行

huggingface-cli login

然后您可以使用 push_to_hub() 方法将模型推送到 Hub

model.push_to_hub("my-awesome-model")

这将把模型文件上传到 Hub,位于您命名空间下名为 *my-awesome-model* 的仓库中。然后,任何人都可以使用 from_pretrained() 方法加载您的模型!

from transformers import AutoModel

model = AutoModel.from_pretrained("your-username/my-awesome-model")

您可以使用 Hub API 完成更多操作

  • 从本地仓库推送模型
  • 更新特定文件而无需重新上传所有内容
  • 添加模型卡以记录模型的能力、局限性、已知偏差等。

有关此内容的完整教程,请参阅文档,或查看高级第 4 章

编码文本

Transformer 模型通过将输入转换为数字来处理文本。在这里,我们将准确地了解您的文本被分词器处理时会发生什么。我们已经在第 1 章中看到,分词器将文本分割成标记,然后将这些标记转换为数字。我们可以通过一个简单的分词器来查看这种转换

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

encoded_input = tokenizer("Hello, I'm a single sentence!")
print(encoded_input)
{'input_ids': [101, 8667, 117, 1000, 1045, 1005, 1049, 2235, 17662, 12172, 1012, 102], 
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

我们得到一个包含以下字段的字典

  • input_ids:您的 token 的数字表示
  • token_type_ids:这些告诉模型输入中的哪一部分是句子 A,哪一部分是句子 B(在下一节中会详细讨论)
  • attention_mask:这表示哪些 token 应该被关注,哪些不应该(稍后会详细讨论)

我们可以解码 input ID 以取回原始文本

tokenizer.decode(encoded_input["input_ids"])
"[CLS] Hello, I'm a single sentence! [SEP]"

您会注意到分词器添加了模型所需的特殊标记——[CLS][SEP]。并非所有模型都需要特殊标记;当模型使用它们进行预训练时,它们才会被使用,在这种情况下,分词器需要添加它们,因为该模型期望这些标记。

您可以一次编码多个句子,可以通过批量处理它们(我们稍后会讨论)或通过传递列表

encoded_input = tokenizer("How are you?", "I'm fine, thank you!")
print(encoded_input)
{'input_ids': [[101, 1731, 1132, 1128, 136, 102], [101, 1045, 1005, 1049, 2503, 117, 5763, 1128, 136, 102]], 
 'token_type_ids': [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 
 'attention_mask': [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

请注意,当传递多个句子时,分词器会为每个字典值返回每个句子的列表。我们还可以要求分词器直接从 PyTorch 返回张量

encoded_input = tokenizer("How are you?", "I'm fine, thank you!", return_tensors="pt")
print(encoded_input)
{'input_ids': tensor([[  101,  1731,  1132,  1128,   136,   102],
         [  101,  1045,  1005,  1049,  2503,   117,  5763,  1128,   136,   102]]), 
 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 
 'attention_mask': tensor([[1, 1, 1, 1, 1, 1],
         [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}

但有一个问题:这两个列表长度不同!数组和张量需要是矩形,因此我们不能简单地将这些列表转换为 PyTorch 张量(或 NumPy 数组)。分词器为此提供了一个选项:填充。

填充输入

如果我们要求分词器填充输入,它会通过向比最长句子短的句子添加特殊的填充标记,使所有句子的长度相同

encoded_input = tokenizer(
    ["How are you?", "I'm fine, thank you!"], padding=True, return_tensors="pt"
)
print(encoded_input)
{'input_ids': tensor([[  101,  1731,  1132,  1128,   136,   102,     0,     0,     0,     0],
         [  101,  1045,  1005,  1049,  2503,   117,  5763,  1128,   136,   102]]), 
 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 
 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
         [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}

现在我们有了矩形张量!请注意,填充标记已编码为 ID 为 0 的输入 ID,它们的注意力掩码值也为 0。这是因为这些填充标记不应由模型分析:它们不属于实际句子。

截断输入

张量可能变得太大而无法由模型处理。例如,BERT 仅使用最多 512 个标记的序列进行预训练,因此它无法处理更长的序列。如果您有比模型能处理的更长的序列,您需要使用 truncation 参数截断它们

encoded_input = tokenizer(
    "This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long sentence.",
    truncation=True,
)
print(encoded_input["input_ids"])
[101, 1188, 1110, 170, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1505, 1179, 5650, 119, 102]

通过结合填充和截断参数,您可以确保您的张量具有所需的精确大小

encoded_input = tokenizer(
    ["How are you?", "I'm fine, thank you!"],
    padding=True,
    truncation=True,
    max_length=5,
    return_tensors="pt",
)
print(encoded_input)
{'input_ids': tensor([[  101,  1731,  1132,  1128,   102],
         [  101,  1045,  1005,  1049,   102]]), 
 'token_type_ids': tensor([[0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0]]), 
 'attention_mask': tensor([[1, 1, 1, 1, 1],
         [1, 1, 1, 1, 1]])}

添加特殊标记

特殊标记(或至少是它们的概念)对 BERT 和派生模型尤为重要。这些标记被添加以更好地表示句子边界,例如句子的开头 ([CLS]) 或句子之间的分隔符 ([SEP])。让我们看一个简单的例子

encoded_input = tokenizer("How are you?")
print(encoded_input["input_ids"])
tokenizer.decode(encoded_input["input_ids"])
[101, 1731, 1132, 1128, 136, 102]
'[CLS] How are you? [SEP]'

这些特殊标记由分词器自动添加。并非所有模型都需要特殊标记;它们主要在模型预训练时使用,在这种情况下,分词器会添加它们,因为模型期望它们。

为什么这一切都是必要的?

这是一个具体的例子。考虑这些编码序列

sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]

分词后,我们得到

encoded_sequences = [
    [
        101,
        1045,
        1005,
        2310,
        2042,
        3403,
        2005,
        1037,
        17662,
        12172,
        2607,
        2026,
        2878,
        2166,
        1012,
        102,
    ],
    [101, 1045, 5223, 2023, 2061, 2172, 999, 102],
]

这是一个编码序列的列表:一个列表的列表。张量只接受矩形形状(想想矩阵)。这个“数组”已经是矩形形状,所以将其转换为张量很容易

import torch

model_inputs = torch.tensor(encoded_sequences)

使用张量作为模型输入

将张量与模型一起使用非常简单——我们只需使用输入调用模型即可

output = model(model_inputs)

尽管模型接受许多不同的参数,但只有输入 ID 是必需的。我们稍后会解释其他参数的作用以及何时需要它们,但首先我们需要仔细研究构建 Transformer 模型可以理解的输入的分词器。

< > 在 GitHub 上更新