BERT(Bidirectional Encoder Representations from Transformers)全称是“双向编码器表征法”或简单地称为“双向变换器模型”,是一种基于Transformer架构的预训练语言模型,由Google在2018年推出,代码已开源。BERT在自然语言处理(NLP)领域具有广泛的应用和出色的性能,为多种语言理解任务提供了强大的预训练模型基础。
BERT采用了双向Transformer编码器结构,这意味着在预训练阶段,模型能够同时利用输入序列的左侧和右侧上下文信息,从而更准确地理解语言的含义。
BERT在自然语言处理领域具有广泛的应用,包括:
BERT是一种基于Transformer架构的模型,且只包含Encoder模块,由多个Encoder block模块堆叠而成。其架构如下图所示。

从上图左侧,我们可以看到BERT包含三种模块:
上图右侧是Encoder block的内部模块图。图中蓝色模块被画成了两层,这表示BERT模型中堆叠了多个Transformer编码器块。每个编码器块都包含自注意力机制(Self-Attention Mechanism)和前馈神经网络(Feed-Forward Neural Network),以及层归一化(Layer Normalization)和残差连接(Residual Connections)。
BERT模型有两种规模:Base版和Large版。其中,Base版包含12层Transformer编码器,隐藏层大小为768,自注意力头数为12,总参数量约为110M;Large版则包含24层Transformer编码器,隐藏层大小为1024,自注意力头数为16,总参数量约为340M。
BASE版:L = 12,H = 768,A = 12,总参数量为 1.1 亿
LARGE版:L = 24,H = 1024,A = 16,总参数量为 3.4 亿

单向编码指的是在编码过程中,模型只能利用到当前位置之前的文本信息(或只能利用到当前位置之后的文本信息,但这种情况较少见),而无法同时利用到当前位置前后的文本信息。这种编码方式使得模型在处理文本时具有一种“前瞻性”或“回顾性”,但缺乏全局的上下文理解能力。
GPT是一个典型的采用单向编码的预训练语言模型。GPT使用Transformer的解码器部分作为其主要结构,通过自回归的方式进行训练,即模型在生成下一个词时只能看到之前的词,无法看到之后的词。
双向编码则允许模型在编码过程中同时利用到当前位置前后的文本信息,从而能够更全面地理解文本的上下文。这种编码方式使得模型在处理文本时具有更强的语义理解能力和更丰富的信息来源。
BERT是一个典型的采用双向编码的预训练语言模型。BERT通过掩码语言模型(MLM)的方式进行训练,即随机掩盖文本中的部分词汇,然后让模型预测这些被掩盖的词汇。
举个例子:考虑一个文本序列“今天天气很好,我们决定去公园散步。”
在单向编码中,每个词或标记的编码仅依赖于其之前的词或标记。因此,在编码“决定”这个词时,模型只会考虑“今天”、“天气”、“很好”和“我们”这些在它之前的词。 在双向编码中,每个词或标记的编码都会同时考虑其前后的词或标记。因此,在编码“决定”这个词时,模型会同时考虑“今天”、“天气”、“很好”以及之后的“去公园散步”等词,从而更全面地理解整个句子的语义。

BERT的输入Embedding模块由三部分组成:
下图是一个示例。

BERT的预训练过程主要包括两个阶段:Masked Language Model(MLM)和Next Sentence Prediction(NSP)。
Masked Language Model,即遮蔽语言模型,是BERT预训练的一个重要部分。在这一阶段,模型的任务是预测输入句子中被随机遮蔽(masked)掉的部分单词。

我们来看一个例子,假设有一句话:my dog is hairy
Next Sentence Prediction,即下一句预测,是BERT预训练的另一个重要部分,旨在提高模型对句子间关系的理解能力。
注:在BERT的后续版本中,Next Sentence Prediction(NSP)任务被废弃了。因为研究人员发现这个任务对下游任务的性能提升有限,因此在BERT的一些后续变体中被弃用了。
根据自然语言处理(NLP)下游任务输入和输出形式的不同,微调任务可以分为四类,分别是句对分类、单句分类、文本问答和单句标注,如下图中abcd所示。

bert-base-chinese 是 Hugging Face 模型库中专门针对中文文本预训练的 BERT 模型,由谷歌官方团队开发并开源。
from transformers import BertTokenizer, BertModel
import torch
from sklearn.metrics.pairwise import cosine_similarity
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')
# 指定本地模型文件路径
"""
tokenizer = BertTokenizer.from_pretrained('./bert-base-chinese')
model = BertModel.from_pretrained('./bert-base-chinese')
"""
qa_pairs = {
"你好": "你好!有什么可以帮您?",
"你是谁": "我是一个基于BERT的聊天机器人",
"再见": "再见!祝您有美好的一天!",
"天气如何": "建议查看当地天气预报网站",
"推荐餐厅": "您喜欢什么类型的菜系?"
}
def get_embedding(text):
inputs = tokenizer(text, return_tensors="pt",
max_length=64, padding='max_length', truncation=True)
with torch.no_grad():
outputs = model(**inputs)
return outputs.last_hidden_state[:,0,:].numpy()
# 预计算所有问题的嵌入
question_embeddings = {q: get_embedding(q) for q in qa_pairs.keys()}
def find_best_answer(user_input):
input_embed = get_embedding(user_input)
best_sim = -1
best_answer = "我不太明白您的意思"
for q, q_embed in question_embeddings.items():
sim = cosine_similarity(input_embed, q_embed)[0][0]
if sim > best_sim:
best_sim = sim
best_answer = qa_pairs[q]
return best_answer
print("机器人:您好!请输入您的问题(输入'exit'退出)")
while True:
user_input = input("您:")
if user_input.lower() == 'exit':
break
response = find_best_answer(user_input)
print(f"机器人:{response}")
运行效果:
机器人:您好!请输入您的问题(输入'exit'退出)
您:你好。
机器人:你好!有什么可以帮您?
您:天气如何
机器人:建议查看当地天气预报网站
您:再见
机器人:再见!祝您有美好的一天!
您:exit