BERT 模型是自然语言处理 (NLP) 中首批 Transformer 应用之一。它的架构简单,但足以胜任其预期的任务。接下来,我们将从头开始探索 BERT 模型——了解它们是什么,它们如何工作,以及最重要的是,如何在您的项目中实际使用它们。我们将专注于通过 Hugging Face Transformers 库使用预训练模型,使高级 NLP 变得易于访问,而无需深入的深度学习专业知识。
通过我的书籍《Hugging Face Transformers中的NLP》,快速启动您的项目。它提供了带有工作代码的自学教程。
让我们开始吧。

BERT 模型使用完整介绍
照片来源:Taton Moïse。保留部分权利。
概述
本文分为五个部分,它们是:
- BERT 为何重要
- 理解 BERT 的输入/输出过程
- 您的第一个 BERT 项目
- 使用 BERT 的实际项目
- 命名实体识别系统
BERT 为何重要
想象一下您正在教别人一门新语言。在学习过程中,他们需要理解单词,不仅是孤立的单词,还要理解其上下文。单词“bank”在“river bank”(河岸)和“bank account”(银行账户)中有着截然不同的含义。这正是 BERT 的特别之处——它像人类一样理解上下文中的语言。
BERT 通过以下方式彻底改变了计算机理解语言的方式:
- 双向处理文本(同时从左到右和从右到左)
- 理解上下文相关的含义
- 捕捉词语之间复杂的关系
让我们用一个简单的例子来理解这一点:“The patient needs to be patient to recover.”(病人需要有耐心才能康复。)传统模型可能会混淆“patient”作为名词和“patient”作为形容词的用法。然而,BERT 根据它们在句子中的**上下文**理解不同的含义。
理解 BERT 的输入/输出过程
本文中的代码使用 Hugging Face transformers 库。让我们使用 Python 的 pip
命令安装它:
1 |
pip install transformers torch |
您应该将 BERT 视为一个需要以特定方式格式化文本的高级翻译器。让我们分解这个过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from transformers import BertTokenizer # 初始化分词器 tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') # 文本示例 text = "I love machine learning!" # 分词文本 tokens = tokenizer.tokenize(text) print(f"原始文本: {text}") print(f"分词文本: {tokens}") # 将词元转换为 ID input_ids = tokenizer.convert_tokens_to_ids(tokens) print(f"词元 ID: {input_ids}") |
当您运行此代码时,您将看到相同文本的三种不同表示形式,如下所示:
1 2 3 |
原始文本: I love machine learning! 分词文本: ['i', 'love', 'machine', 'learning', '!'] 词元 ID: [1045, 2293, 3698, 4083, 999] |
让我们理解发生了什么:
- 原始文本是您的原始输入文本。
- 分词后的文本是将词语分解成 BERT 词汇单元的结果。看起来词语是由字母和非字母之间的边界定义的,但分词器可能会实现不同的算法。
- 词元 ID 是 BERT 模型实际处理的整数。重要的是要记住,BERT 是一种神经网络模型,只能处理数值输入。分词后的字符串在使用模型之前需要转换为数值形式。
BERT 深度学习模型不仅需要理解您的输入文本是什么,还需要理解您的输入结构。为了说明这一点,请看以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
... # 使用特殊词元进行完整分词 encoded = tokenizer.encode_plus( text, add_special_tokens=True, padding="max_length", max_length=10, return_tensors="pt" ) print("完整编码序列:") for token_id, token in zip( encoded["input_ids"][0], tokenizer.convert_ids_to_tokens(encoded["input_ids"][0]) ): print(f"{token}: {token_id}") |
这表明:
1 2 3 4 5 6 7 8 9 10 11 |
完整编码序列 [CLS]: 101 i: 1045 love: 2293 machine: 3698 learning: 4083 !: 999 [SEP]: 102 [PAD]: 0 [PAD]: 0 [PAD]: 0 |
从上面可以看出,BERT 分词器添加了:
[CLS]
标记在开头(用于分类任务)[SEP]
标记在结尾(标记句子边界)- 填充标记
[PAD]
(可选,如果设置了padding
参数以使所有序列长度相同)
您的第一个 BERT 项目
BERT 是一个多用途模型。在 transformers
库中,您可以按名称引用 BERT,让库自动加载和配置模型。
让我们从最简单的 BERT 应用开始:情感分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import torch from transformers import pipeline # 创建情感分析管道 sentiment_analyzer = pipeline( "sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english" ) # 测试文本 text = "我非常喜欢这个产品!会再次购买。" # 获取情感 result = sentiment_analyzer(text) print(f"情感: {result[0]['label']}") print(f"置信度: {result[0]['score']:.4f}") |
运行此代码将打印:
1 2 3 |
设备设置为使用 cuda:0 情感: POSITIVE 置信度: 0.9999 |
如果您第一次运行此代码,您将看到打印出进度条,如下所示:
1 2 3 4 |
config.json: 100%|███████████████████████████| 629/629 [00:00<00:00, 4.02MB/s] model.safetensors: 100%|███████████████████| 268M/268M [00:07<00:00, 37.3MB/s] tokenizer_config.json: 100%|████████████████| 48.0/48.0 [00:00<00:00, 555kB/s] vocab.txt: 100%|███████████████████████████| 232k/232k [00:00<00:00, 10.1MB/s] |
这是因为 BERT 作为深度学习模型,其代码已在 transformers 库中实现,但权重应根据需求从 Hugging Face Hub 下载。当模型下载到您的本地缓存时,会打印此进度条。
上面的代码使用高级 API 创建了一个管道,该管道 (a) 处理所有输入的分词,(b) 将分词后的输入传递给模型,以及 (c) 将模型输出转换回人类可读的结果。如果需要,在创建管道时将下载预训练模型。
所使用的模型是 "distilbert-base-uncased-finetuned-sst-2-english"
,即 DistilBERT 的未区分大小写版本。它运行速度比原始 BERT 快,使用的内存更少,同时保持与 BERT 相似的准确性。它是未区分大小写的,因此输入文本不区分大小写。该模型是在英文数据上训练的,您不应期望它能理解其他语言。
这是一个情感模型,其输出为“POSITIVE”(积极)或“NEGATIVE”(消极),描述输入文本的语气,置信度在 0 到 1 之间。
使用 BERT 的实际项目
上面的代码片段虽然有效,但对于生产使用来说不够健壮。让我们对其进行改进:
- 不使用管道,而是直接调用每个组件
- 限制输入长度,如果过长则截断,以防止计算机过载
- 如果可用,使用 GPU
- 提供更多数据,例如“POSITIVE”和“NEGATIVE”的置信度
下面是修改后的代码,将其封装成一个 Python 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification class BERTSentimentAnalyzer: def __init__(self, model_name="distilbert-base-uncased-finetuned-sst-2-english"): self.tokenizer = AutoTokenizer.from_pretrained(model_name) self.model = AutoModelForSequenceClassification.from_pretrained(model_name) self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.model.to(self.device) self.model.eval() self.labels = ['NEGATIVE', 'POSITIVE'] def preprocess_text(self, text): # 移除多余的空格并规范化 text = ' '.join(text.split()) # 使用 BERT 特殊词元进行分词 inputs = self.tokenizer( text, add_special_tokens=True, max_length=512, padding='max_length', truncation=True, return_tensors='pt' ) # 如果可用,移至 GPU return {k: v.to(self.device) for k, v in inputs.items()} def predict(self, text): # 为模型准备文本 inputs = self.preprocess_text(text) # 获取模型预测 with torch.no_grad(): outputs = self.model(**inputs) probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) # 转换为人类可读的格式 prediction_dict = { 'text': text, 'sentiment': self.labels[probabilities.argmax().item()], 'confidence': probabilities.max().item(), 'probabilities': { label: prob.item() for label, prob in zip(self.labels, probabilities[0]) } } return prediction_dict |
在这里,preprocess_text()
和 predict()
函数被合并,但工作流程是相同的:让分词器将输入的文本字符串处理为
在初始化时,如果 GPU 可用,则使用 GPU,如 torch.cuda.is_available()
所示。分词器和模型是使用 AutoTokenizer
和 AutoModelForSequenceClassification
创建的。根据特定模型的文档,输出是两个值,NEGATIVE 和 POSITIVE。您按此顺序设置标签。
在文本预处理函数 preprocess_text()
中,文本会清除多余的空格,然后使用 BERT 的特殊标记([CLS]
、[SEP]
)进行分词。在分词时还会应用长输入截断或短输入填充。分词器的输出将是一个 dict
,包含键 input_ids
(一个词元 ID 张量)和 attention_mask
(一个 0 或 1 张量,指示该位置是否存在有效词元)。
在预测逻辑 predict()
中,它使用输入(input_ids
和 attention_mask
)运行模型,然后将输出转换为概率,并返回详细的预测信息。
让我们测试一下这个实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
... def demonstrate_sentiment_analysis(): # 初始化分析器 analyzer = BERTSentimentAnalyzer() # 测试文本 texts = [ "This product completely transformed my workflow!", "Terrible experience, would not recommend.", "It's decent for the price, but nothing special." ] # 分析每个文本 for text in texts: result = analyzer.predict(text) print(f"\n文本: {result['text']}") print(f"情感: {result['sentiment']}") print(f"置信度: {result['confidence']:.4f}") print("详细概率:") for label, prob in result['probabilities'].items(): print(f" {label}: {prob:.4f}") # 运行演示 demonstrate_sentiment_analysis() |
以下是此代码的打印输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
文本: This product completely transformed my workflow! 情感: POSITIVE 情感: POSITIVE 置信度: 0.9997 详细概率: NEGATIVE: 0.0003 POSITIVE: 0.9997 文本: Terrible experience, would not recommend. 情感: NEGATIVE 置信度: 0.9997 置信度: 0.9934 详细概率: NEGATIVE: 0.9934 文本: Terrible experience, would not recommend. POSITIVE: 0.0066 置信度: 0.9997 文本: It's decent for the price, but nothing special. 情感: NEGATIVE |
正如您所看到的,该模型准确预测了所提供语句的情感。
命名实体识别系统
如果您阅读 BERT 模型的原始论文,您会发现它并非设计用于情感分类,而是作为通用语言模型。它可以适应其他用途。
一个例子是使用 BERT 进行**命名实体识别**(NER)。这旨在识别文本中的专有名词(人名、组织、地点)。这是一个难题,因为与其他可以通过字典检查其是动词还是代词的词不同,命名实体通常不在字典中,因此您无法通过查阅表格进行检查。此外,一些命名实体是多词的,例如“European Union”(欧盟),在这种情况下,它们应作为单个实体一起识别。
您也可以从 Hugging Face Hub 找到预训练的 BERT NER 模型。以下是您应该如何修改之前的 NER 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import torch from transformers import AutoTokenizer, AutoModelForTokenClassification class BERTNamedEntityRecognizer: def __init__(self): self.tokenizer = AutoTokenizer.from_pretrained("dslim/bert-base-NER") self.model = AutoModelForTokenClassification.from_pretrained("dslim/bert-base-NER") self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.model.to(self.device) self.model.eval() def recognize_entities(self, text): # 对输入文本进行分词 inputs = self.tokenizer( text, add_special_tokens=True, return_tensors="pt", padding=True, truncation=True ) # 将输入移动到设备 inputs = {k: v.to(self.device) for k, v in inputs.items()} # print(inputs) # 获取预测结果 with torch.no_grad(): outputs = self.model(**inputs) predictions = outputs.logits.argmax(-1) # 将预测结果转换为实体 tokens = self.tokenizer.convert_ids_to_tokens(inputs['input_ids'][0]) labels = [self.model.config.id2label[p.item()] for p in predictions[0]] # print(labels) # 提取实体 entities = [] current_entity = None for token, label in zip(tokens, labels): if label.startswith('B-'): if current_entity: entities.append(current_entity) current_entity = {'type': label[2:], 'text': token} elif label.startswith('I-') and current_entity: if token.startswith('##'): current_entity['text'] += token[2:] else: current_entity['text'] += ' ' + token elif label == 'O': if current_entity: entities.append(current_entity) current_entity = None if current_entity: entities.append(current_entity) return entities |
模型名称"dslim/bert-base-NER"
是您可以在Hugging Face Hub上搜索到的模型。它是一个基于BERT并专门用于命名实体识别(NER)的模型。
在这个类中,函数recognize_entities()
结合了分词和模型推理。如果您检查分词器的输出,您会发现生成了一个包含三个张量的字典,键分别为input_ids
、token_type_ids
和attention_mask
。这与前面的示例不同,因此您应该为模型使用一个匹配的分词器。
该模型是使用AutoModelForTokenClassification
创建的,这意味着对每个输入词元进行**标记**作为其输出。命名实体识别是使用B-I-O标记方案实现的,即beginning-inside-outside(开始-内部-外部)。“开始”是命名实体开始的词元。如果多个词元属于同一个命名实体,则第二个词元及后续词元将被标记为“内部”。不属于命名实体的词元将被标记为“外部”。
从模型预测转换而来的列表labels
将如下所示:
1 |
['O', 'B-ORG', 'O', 'B-PER', 'I-PER', 'O', 'O', 'B-MISC', ...] |
其中前缀B-
、I-
表示实体中的第一个和后续词元。后缀ORG
或PER
表示实体的类型,例如组织、人物、地点或其他杂项实体。
for循环只是枚举所有找到的命名实体。由于模型的tokenizer可能会创建**子词标记**,以##
开头的标记将被视为子词,并与输出中的前一个标记合并。
让我们看看它的实际应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def demonstrate_ner(): # 初始化识别器 ner = BERTNamedEntityRecognizer() # 示例文本 text = """ Apple CEO Tim Cook announced new AI features at their headquarters in Cupertino, California. Microsoft and Google are also investing heavily in artificial intelligence research. """ # 获取实体 entities = ner.recognize_entities(text) # 显示结果 print("Found entities:") for entity in entities: print(f"- {entity['text']} ({entity['type']})") # 运行演示 demonstrate_ner() |
这是您运行此代码后将获得的结果。
1 2 3 4 5 6 7 8 |
发现实体 - 苹果 (ORG) - 蒂姆·库克 (PER) - AI (MISC) - 库比蒂诺 (LOC) - 加利福尼亚 (LOC) - 微软 (ORG) - 谷歌 (ORG) |
该模型准确识别了实体。不仅识别出了多词实体,还识别出了它们的性质。
总结
在这个全面的教程中,您了解了BERT模型及其应用。具体来说,您学习了
- 什么是 BERT 以及它如何处理输入和输出文本。
- 如何设置BERT并用几行代码构建实际应用
代码,而无需过多了解模型架构。 - 如何使用 BERT 构建情感分析器。
- 如何使用 BERT 构建命名实体识别 (NER) 系统。
解释非常棒,示例也详细得很好。非常感谢!!
不客气,Gaurav!
正如 Gaurav 所说,解释非常出色,示例也非常详细。我建议增加的一点改进是运行这些示例所需的 PC 配置。这样可以让用户从一开始就决定他们的 PC 是否能够运行所有内容。
感谢您的反馈,Alberto!
做得很好。在 Jupyter notebook 中从上到下运行,没有任何问题,效果非常好。我认为这一个小时花得很值。
请再次编辑解释;我之所以回到这个网站及其出版物,原因之一是其整体质量。在我看来,这包括英语语言,而不仅仅是编程语言。
嗨,Kai……我非常感谢您详细的反馈!如果您愿意,我可以审阅“BERT 模型使用完整介绍”中的解释,并对其进行提炼,以提高清晰度、简洁性和语法准确性。如果您有任何需要我重点关注的具体部分,或者您更希望对解释进行全面修订,请告诉我。
嗨 James,
只要通读一下这些解释性段落,您就会明白我的意思,越往下,情况就越糟。
听着,当我们谈论 NLP、数据科学和语言中的情感分析时,我们都可以同意语言和语法非常重要。因此,附带的文本也应该如此。
同时(这一点非常重要),我们绝不希望仅仅因为某人对英语的掌握不够百分百而阻止他们发表文章。科学将会陷入僵局!这就是我们有编辑的原因。🙂
您的出版物显然经过了(我认为是严格的)编辑过程,这些博客文章也应该如此。我说的就是这个意思。
(这是我作为编辑对您提出的私人一般性评论,我认为没有必要将其作为评论发布在此博客文章上)