bert模型加载、预训练、推理的教程
在这篇文章中,我将带你一步一步地了解如何使用 Hugging Face 的 transformers
库,下载并加载预训练模型,然后进行简单的推理任务。本文适合刚接触深度学习和自然语言处理(NLP)的初学者。我们将使用 BERT
作为示例,并通过详细的代码注释帮助你理解每一步。
一、完整代码
与下面代码对应的视频教程:bert代码解释
# 1. 导入所需库
from modelscope import snapshot_download
from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch
# 2. 下载并加载预训练模型
model_dir = snapshot_download('tianshi/bert-base-chinese', cache_dir='./model')
# 3. 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained("./model/tianshi/bert-base-chinese")
model = AutoModelForMaskedLM.from_pretrained("./model/tianshi/bert-base-chinese")
# 4. 定义输入句子
sentence1 = '天气真好'
sentence2 = '我们去爬山'
# 5. 编码输入句子
inputs = tokenizer.encode_plus(sentence1, sentence2, max_length=16, truncation=True, pad_to_max_length=True)
# 6. 创建 PyTorch 张量
input_ids = torch.tensor(inputs['input_ids']).unsqueeze(0)
attention_mask = torch.tensor(inputs['attention_mask']).unsqueeze(0)
# 7. 模型推理
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
# 8. 获取模型的最后隐藏状态
last_hidden_state = outputs.last_hidden_state
# 9. 打印最后隐藏状态
print(last_hidden_state)
# 10. 查看输出张量的形状
print(last_hidden_state.shape)
代码说明
- 模型加载与推理:从 ModelScope 下载预训练的 BERT 中文模型,并使用
AutoTokenizer
和AutoModelForMaskedLM
来进行分词和推理。 - 张量转换:将输入句子编码为 BERT 模型可接受的格式,并将其转换为 PyTorch 张量。
- 模型输出:执行推理,得到最后隐藏状态并输出它的形状。
这份完整的代码涵盖了如何加载、预训练、推理 BERT 模型的每一步。
二、代码详细解释
准备工作
首先,你需要确保已安装 transformers
库。你可以通过以下命令安装:
pip install transformers
接下来是代码的逐步解释。
1. 导入所需库
from modelscope import snapshot_download
from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch
modelscope
是一个模型管理和部署平台,我们通过snapshot_download
函数从中下载预训练的 BERT 模型。transformers
是 Hugging Face 提供的库,里面有很多预训练模型。我们将使用AutoTokenizer
和AutoModelForMaskedLM
自动加载合适的分词器和模型。torch
是 PyTorch 框架,我们会用它来创建张量并进行数学运算。
2. 下载并加载预训练模型
model_dir = snapshot_download('tianshi/bert-base-chinese', cache_dir='./model')
- 这里使用
snapshot_download
函数从 ModelScope 平台下载名为tianshi/bert-base-chinese
的模型,并将其存储在本地目录./model
中。
3. 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained("./model/tianshi/bert-base-chinese")
model = AutoModelForMaskedLM.from_pretrained("./model/tianshi/bert-base-chinese")
AutoTokenizer
用于将文本转换为模型可以理解的 token(即一串数字)。AutoModelForMaskedLM
加载的是 BERT 模型,具体用于掩码语言模型任务(Masked Language Model),即预测句子中被掩盖的单词。
4. 定义输入句子
sentence1 = '天气真好'
sentence2 = '我们去爬山'
- 这里我们定义了两个简单的中文句子,用于后续的编码和推理。
5. 编码输入句子
inputs = tokenizer.encode_plus(sentence1, sentence2, max_length=16, truncation=True, pad_to_max_length=True)
encode_plus
函数将两个句子编码为模型可接受的格式。max_length=16
:设置最大句子长度为 16 个 token,超出部分会被截断。truncation=True
:如果句子长度超出max_length
,则截断。pad_to_max_length=True
:如果句子长度不足 16,则在末尾补充特殊的[PAD]
token。
6. 创建 PyTorch 张量
input_ids = torch.tensor(inputs['input_ids']).unsqueeze(0)
attention_mask = torch.tensor(inputs['attention_mask']).unsqueeze(0)
input_ids
是表示句子的 token ID 序列,我们使用torch.tensor
将其转换为 PyTorch 张量。attention_mask
用于标识哪些位置是实际 token,哪些是填充的[PAD]
,同样转换为张量。unsqueeze(0)
会增加一个维度,表示批次(batch size),因为模型要求输入的形状是(batch_size, sequence_length)
。
7. 模型推理
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
- 将
input_ids
和attention_mask
作为输入,传递给 BERT 模型,进行前向传播,得到模型输出。
8. 获取模型的最后隐藏状态
last_hidden_state = outputs.last_hidden_state
print(last_hidden_state)
last_hidden_state
是模型最后一层的隐藏状态,表示输入句子经过 BERT 模型后的表示(即嵌入)。
9. 查看输出张量的形状
print(last_hidden_state.shape)
- 输出张量的形状为
(batch_size, sequence_length, hidden_size)
,我们可以通过shape
属性查看张量的维度。在 BERT 模型中,hidden_size
一般是 768。
运行以上代码后,最后你会看到模型的输出张量,以及它的形状。对于初学者来说,这种方式可以帮助你理解如何使用预训练模型进行推理。
补充内容:深入理解 Tokenizer 和 AutoModelForMaskedLM
在上一部分中,我们已经介绍了如何使用 Hugging Face 的 transformers
库进行预训练模型的加载和推理。接下来,我们将进一步解释一些关键概念,包括 Tokenizer
和 AutoModelForMaskedLM
,帮助你深入理解它们的工作原理和实际应用。
1. Tokenizer 的作用和工作原理
在自然语言处理(NLP)和机器学习领域,Tokenizer
是一个非常重要的工具。它的主要作用是将原始文本转换为机器可以处理的形式,也就是所谓的 token。以下是对 Tokenizer
的详细解释:
-
文本分割:Tokenizer 的首要任务是将文本分割成多个独立的 token。这些 token 可以是单词、子词甚至字符,具体取决于使用的模型。对于像英语这样的语言,空格是天然的分词符号,但对于中文、日语等不使用空格分词的语言,分词任务就更加复杂。
-
标准化处理:通常,Tokenizer 还会执行一些标准化操作,例如将所有字符转为小写、移除标点符号、消除多余的空格等。这一步的目的是为了让模型能够更好地理解输入。
-
Token 类型:
- 单词级别:基于单词进行分词。
- 字符级别:每个字符作为一个 token。
- 子词级别:模型将单词拆分为更小的子词单元,处理那些模型词汇表中不存在的词。
-
词汇表(Vocabulary):每个 Tokenizer 都有一个词汇表,它定义了模型可以理解的所有 token。在文本转换为 token 的过程中,Tokenizer 会根据词汇表将单词映射为对应的索引。
-
处理未知词汇:当 Tokenizer 遇到词汇表中不存在的词时,通常会用特殊的
[UNK]
(Unknown) token 表示这些词。 -
数字和特殊字符处理:一些 Tokenizer 还会特殊处理数字或 URL 等内容,比如将数字拆分为子词,或用标记表示电子邮件地址、URL 等。
Tokenization 是文本处理的第一步,也是至关重要的一步,因为它决定了模型接收到的输入数据的质量。常见的 NLP 任务,如机器翻译、文本分类、命名实体识别等,都依赖于良好的分词。
2. AutoModelForMaskedLM:掩码语言模型详解
AutoModelForMaskedLM
是 Hugging Face 提供的一个类,专门用于处理掩码语言模型(Masked Language Model, MLM)任务。掩码语言模型的主要目的是在给定的上下文中预测被掩盖的单词。BERT 模型就是基于这种预训练任务。
-
掩码语言模型(MLM):在 MLM 任务中,句子中的某些 token 会被替换为特殊的
[MASK]
,模型需要根据上下文推断出这些掩码位置的正确词汇。例如,对于句子 “The [MASK] sat on the [MASK]”,模型应该能够预测出[MASK]
的位置分别对应 “cat” 和 “mat”。 -
AutoModelForMaskedLM:
AutoModelForMaskedLM
允许用户自动加载适合 MLM 任务的模型,而不需要手动指定模型的架构。它可以用于继续预训练模型,或微调模型以适应具体的任务。
3. from_pretrained:加载预训练模型和分词器
from_pretrained
是 Hugging Face 中一个核心函数,用于加载预训练模型和分词器。这个函数可以帮助我们轻松地从模型库或本地路径加载已经训练好的模型,并进行任务微调或推理。无论是分词器还是模型,from_pretrained
都提供了高度的灵活性。
以下是 from_pretrained
函数的要点:
- 参数说明:
pretrained_model_name_or_path
:指定要加载的模型名称或本地路径。config
和tokenizer_config
:允许用户传递配置对象,进一步自定义模型或分词
器。
-
cache_dir
:指定缓存目录,用于存储下载的模型。 -
使用场景:该函数适用于文本分类、情感分析、机器翻译等任务,它允许我们利用预训练好的模型权重,避免从头开始训练模型。
示例代码:加载 BERT 模型并执行掩码语言任务
from transformers import AutoTokenizer, AutoModelForMaskedLM
# 加载预训练的分词器和模型
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModelForMaskedLM.from_pretrained('bert-base-uncased')
# 编码输入句子,其中部分词汇被掩盖
inputs = tokenizer("The [MASK] sat on the [MASK].", return_tensors="pt")
# 使用模型进行推理,预测被掩盖的单词
outputs = model(**inputs)
predictions = outputs.logits
# 打印输出结果
print(predictions)
在这个示例中,我们加载了一个 BERT 模型,并对一个包含掩码的句子进行了推理。AutoModelForMaskedLM
帮助我们预测句子中 [MASK]
位置的单词。
4. Tensor 形状的解释
在自然语言处理的任务中,Tensor 的形状非常重要。在处理文本时,常见的 Tensor 形状为 (batch_size, sequence_length, hidden_size)
,它表示:
- batch_size:一次处理的样本数量。
- sequence_length:每个输入句子的 token 数量。
- hidden_size:每个 token 的隐藏层表示的维度。
例如,BERT 模型的 hidden_size
通常为 768,意味着每个 token 在模型中被表示为 768 维的向量。这些向量捕捉了该 token 的语义信息,并用于后续任务的预测。
三、补充解释
问题1:
对代码的操作流程存在疑问,具体不清楚这些操作的目的是什么,执行后会得到怎样的结果。此外,尚不明确这些代码是用于模型的训练过程,还是用于模型的推理任务。
回答1:
上面的代码实际上是在使用预训练模型进行推理,而不是训练模型。接下来解释为什么这是推理操作,而不是训练操作:
-
模型下载与加载:
- 代码中通过
snapshot_download
下载了预训练的 BERT 模型,并通过AutoModelForMaskedLM
加载了模型。这说明你在使用已经训练好的模型(即预训练模型),并不是从头开始训练模型。
- 代码中通过
-
推理任务:
- 你定义了输入句子
sentence1
和sentence2
,然后使用分词器将这些句子编码为模型可以理解的输入格式,并通过model
进行前向传播(推理),得到了last_hidden_state
。 - 在这一步,模型实际上是在根据输入句子的上下文进行理解和表示,也就是推理过程。
- 你定义了输入句子
-
不是训练:
- 如果是训练模型,通常会有优化器(如 Adam 或 SGD),损失函数(如交叉熵损失),以及反向传播和参数更新的代码。但在这个代码中,并没有出现这些元素。因此,你只是加载了已经训练好的模型,并且让模型对新输入的句子进行推理。
总结:你在代码中是使用预训练好的 BERT 模型进行推理,而不是训练模型。如果你想训练模型,需要定义损失函数、优化器等,并运行反向传播来更新模型的权重。
问题2:
在使用 BERT 模型进行推理时,输入的句子并未包含掩码标记([MASK]),因此没有需要模型预测的缺失词汇。基于此,提出了以下问题:在没有掩码标记的情况下进行推理的意义是什么?这种推理任务的实际用途是什么?并希望举例说明该推理任务的输出结果。
回答2:
在这个代码示例中,句子 sentence1
和 sentence2
并没有包含任何掩码(即 [MASK]
),而所使用的 AutoModelForMaskedLM
(即掩码语言模型)通常是用于填补句子中的掩码位置的词预测任务。这意味着,代码实际上没有使用模型的掩码功能,而是对完整的句子进行了推理。这种操作的输出会是句子的最后隐藏状态,而不是具体的单词预测。
那么,这种推理任务有什么意义呢?虽然没有掩码,但这种推理任务可以为我们提供句子的嵌入表示,即最后一层的隐藏状态。具体来说,这个表示捕捉了模型对句子理解的语义信息。这种句子表示可以用于很多后续任务,比如:
- 文本分类:通过这种句子的表示,你可以进一步对句子进行情感分析、主题分类等任务。
- 相似度计算:可以通过句子的嵌入表示来计算两个句子之间的相似度(比如在信息检索系统中)。
- 句子生成:嵌入表示也可以作为生成新句子的基础。
输出结果的举例
假设你运行这段代码:
sentence1 = '天气真好'
sentence2 = '我们去爬山'
inputs = tokenizer.encode_plus(sentence1, sentence2, max_length=16, truncation=True, pad_to_max_length=True)
input_ids = torch.tensor(inputs['input_ids']).unsqueeze(0)
attention_mask = torch.tensor(inputs['attention_mask']).unsqueeze(0)
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
last_hidden_state = outputs.last_hidden_state
print(last_hidden_state)
输出的示例(假设)
模型的输出 last_hidden_state
可能会像下面这样:
tensor([[[ 0.1345, 0.0237, 0.5124, ..., -0.1345, 0.0452, 0.2871],
[ 0.0546, -0.2543, 0.1983, ..., -0.2311, 0.2314, -0.0541],
[ 0.2451, 0.3213, -0.0544, ..., 0.0478, 0.3101, 0.1454],
...,
[-0.0454, 0.2101, -0.0873, ..., 0.1321, 0.1945, 0.2579],
[ 0.1341, -0.2455, 0.1897, ..., 0.2411, -0.0114, 0.2413],
[ 0.2453, 0.0542, 0.3121, ..., -0.2123, 0.1124, 0.2511]]])
这个张量的形状是 (batch_size, sequence_length, hidden_size)
,具体来说:
batch_size
为 1,表示一次只输入了一个样本。sequence_length
为句子中 token 的长度,假设这里为 16(因为我们设置了max_length=16
)。hidden_size
是 BERT 模型的隐藏层维度,通常是 768。
这种输出的意义
- 隐藏状态 是 BERT 对输入句子逐 token 的理解,每个 token 被表示为 768 维的向量,这个向量捕捉了该 token 在句子中的上下文信息。
- 虽然没有掩码任务,但这些隐藏状态可以用来做更多的下游任务,比如分类或相似度计算等。
如果你希望让模型来预测掩盖的词,可以使用带有 [MASK]
的句子。例如:
inputs = tokenizer("天气真[MASK]", return_tensors="pt")
outputs = model(**inputs)
这样,模型会在 [MASK]
位置给出预测结果。
四、补充代码:英文 BERT 模型的掩码语言模型任务示例,用于预测句子中被掩盖的单词
完整的补充代码如下:
# 额外的示例代码:加载 BERT 模型并执行掩码语言任务
# 1. 加载预训练的分词器和模型
from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch
import torch.nn.functional as F
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModelForMaskedLM.from_pretrained('bert-base-uncased')
# 2. 编码输入句子,其中部分词汇被掩盖
inputs = tokenizer("The [MASK] sat on the [MASK].", return_tensors="pt")
# 3. 使用模型进行推理,预测被掩盖的单词
outputs = model(**inputs)
predictions = outputs.logits
# 4. 打印输出的预测张量
print(predictions)
# 5. 获取预测结果的最大概率索引,找到掩盖位置的预测词汇
masked_index_1 = torch.argmax(predictions[0, 1]).item()
masked_index_2 = torch.argmax(predictions[0, 5]).item()
# 6. 将索引转回为实际的词
predicted_token_1 = tokenizer.convert_ids_to_tokens(masked_index_1)
predicted_token_2 = tokenizer.convert_ids_to_tokens(masked_index_2)
# 7. 打印预测的词汇
print(f"Predicted word for first [MASK]: {predicted_token_1}")
print(f"Predicted word for second [MASK]: {predicted_token_2}")
# 8. 输出最终句子
final_sentence = f"The {predicted_token_1} sat on the {predicted_token_2}."
print(f"Final sentence: {final_sentence}")
解释:
- 第5步:我们用
torch.argmax
找到了在[MASK]
位置上预测的概率最大的词汇。 - 第6步:使用
convert_ids_to_tokens
将预测的索引转为实际的词汇。 - 第8步:将预测的词汇替换到句子中的
[MASK]
位置,得到完整的句子。
输出示例:
假设 BERT 预测 [MASK]
位置的词为 “cat” 和 “mat”,那么输出结果会是:
Predicted word for first [MASK]: cat
Predicted word for second [MASK]: mat
Final sentence: The cat sat on the mat.
通过这个完整的代码,你可以看到如何让模型预测 [MASK]
位置的词汇并将它们填充回句子中。
五、总结
通过上面的内容,我们深入了解了 Tokenizer
的工作原理,如何使用 AutoModelForMaskedLM
进行掩码语言模型任务,以及 from_pretrained
函数的强大功能。掌握这些基础知识可以帮助我们在 NLP 领域更加灵活地使用预训练模型,快速解决实际问题。