1. BERT
1.1 Transformer
Transformer架构是一种基于自注意力机制(self-attention)的神经网络架构,它代替了以前流行的循环神经网络和长短期记忆网络,已经应用到多个自然语言处理方向。
Transformer架构由两个主要部分组成:编码器(Encoder)和解码器(Decoder)。编码器和解码器均是由多个层(layer)堆叠而成,其中每层均由多个子层组成:比如自注意力机制和前馈神经网络。(本篇先不介绍解码器部分。)
1.1.1 编码器
Transformer中的编码器的作用是提取原句中的特征值。Transformer的编码器不止一个,而是由一组 N N N个编码器串联而成。一个编码器的输出作为下一个编码器的输入。编码器由两部分组成:多头注意力层和前馈网络层。
1.1.1.1 多头注意力层
要理解Transformer的多头注意力层,就必须先理解Transformer中的自注意力机制(self-attention)。Transformer中的自注意力机制一种能够使模型在处理序列数据时,通过计算序列中每个元素与其他所有元素之间的相关性,并据此对元素进行加权求和,从而生成包含所有元素信息但更侧重于重要部分的表示的机制。多头注意力机制就是自注意力机制的扩展,它通过并行计算多个自注意力头来捕捉不同子空间中的信息,最终将这些头的输出进行拼接和线性变换。
自注意力机制的计算过程如下图。其中
Q
Q
Q为查询矩阵、
K
K
K为键矩阵、
V
V
V为值矩阵。
1.1.1.2 位置编码
Transformer中的位置编码用于为输入序列中的每个词提供位置信息,以弥补模型中缺少顺序感的缺陷,使模型能够捕捉词汇的相对顺序和位置信息。
1.1.1.3 前馈网络层
Transformer架构中的前馈网络由两个有ReLU激活函数的全连接层组成。前馈网络的参数在句子的不同位置上是相同的,但在不同的编码器模块上是不同的。
1.1.1.4 叠加和归一化组件
叠加和归一组件实际上包含一个残差连接与层的归一化。层的归一化可以防止每层的值剧烈变化,从而提高了模型的训练速度。
至此,完整的编码器框架如下:
1.2 BERT模型
BERT(Bidirectional Encoder Representations from Transformers,多Transformer的双向编码器表示法)模型是由谷歌发布的预训练语言模型。
1.2.1 预训练的BERT
谷歌对外公开了其预训练的BERT模型,用户可以直接下载使用。其下载地址如下:https://huggingface.co/google-bert
BERT模型名称中的的uncased
表示不区分大小写,cased
表示区分大小写。在不区分大小写时,所有标记都转化为小写;在区分大小写时,标记大小写不变,直接用于训练。不区分大小写的模型是最常用的模型,但如果我们正在执行某些任务,比如命名实体识别(named entity recognition, NER),则必须保留大小写,使用区分大小写的模型。
1.2.2 Bert架构
完整的BERT架构可以分为三大部分:输入层、中间层(Transformer编码器层)和输出层。这里重点介绍输入层和输出层。
1.2.2.1 输入层
输入层将文本转换为 BERT 能够处理的形式,主要包括以下三个部分:
- Token Embeddings: 将输入的每个词或子词(通过WordPiece分词)映射为对应的词向量;
- Segment Embeddings:会分别给第一个句子的所有Token都分配0作为ID,用来标记它们属于第一个句子。给第二个句子的所有Token都分配1作为ID,用来标记它们属于第二个句子。
- Position Embeddings:因为BERT不使用传统的RNN或CNN结构,而是基于自注意力机制,所以需要显式添加位置编码,表示词的相对位置,帮助模型捕捉词序信息。
1.2.2.2 输出层
BERT的输出层根据不同任务进行调整。BERT本身是一个通用的预训练模型,通过微调来适应各种下游任务。常见的任务主要包含以下几种:
- 文本分类任务:使用
[CLS]
的输出,添加一个全连接层,将[CLS]
的输出传入该层,再通过softmax进行分类。 - 序列标注任务:在每个token的输出上添加全连接层,对每个token进行分类。
- 问答任务:模型的输出是两个位置预测,一个表示答案的起始位置,另一个表示答案的结束位置,分别对每个token进行位置预测。
2. BERT的使用
2.1 计算文本相似度
Bert在大量语料库上进行预训练,学到了丰富的语言知识,并能为单词、短语、句子生成深层次的语义表示。通过使用 BERT 输出的[CLS]
标记来表示整个句子的语义信息,所以我们可以用Bert向量来计算文本语义相似度。具体代码如下:
from transformers import BertTokenizer, BertModel
import torch
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# 1. 加载 BERT 模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')
# 2. 定义计算文本相似度的函数
def get_sentence_embedding(sentence):
# 对输入句子进行编码
inputs = tokenizer(sentence, return_tensors='pt', max_length=128,
truncation=True, padding='max_length')
# 使用 BERT 模型获取输出
with torch.no_grad():
outputs = model(**inputs)
# 提取 [CLS] 标记的向量(句子级别向量)
cls_embedding = outputs.last_hidden_state[:, 0, :].numpy()
return cls_embedding
def calculate_similarity(text1, text2):
# 计算两个文本的嵌入向量
embedding1 = get_sentence_embedding(text1)
embedding2 = get_sentence_embedding(text2)
# 计算余弦相似度
similarity = cosine_similarity(embedding1, embedding2)
return similarity[0][0]
# 3. 示例文本
text1 = "这个商品挺好用的"
text2 = "这个商品一点也不好用"
# 4. 计算相似度
similarity_score = calculate_similarity(text1, text2)
print(f"Similarity: {similarity_score:.4f}")
运行上述代码你会发现text1和text2这两个语义完全相反的文本的相似度却高达0.91(即使在GPT系列的embedding模型上也会出现类似情况)。这可能是由以下几个原因导致的:
- 模型无法捕捉否定关系:BERT 等预训练模型的主要任务是捕捉词语和句子中的语义关系,但它们在处理语义反转(如“我喜欢”与“我不喜欢”)时,可能难以充分理解否定词的影响,导致两者生成的嵌入向量仍然较为接近。
- 语义结构相似性:即便两个句子的语义相反,它们可能在结构上非常相似,例如“我喜欢苹果”和“我讨厌苹果”在句法和词汇上只有否定词的不同,因此生成的嵌入向量仍然较接近。
- 预训练数据的局限:BERT 在大量通用语料上进行预训练,未必对所有语境中的细微差异或否定词有足够的敏感性,这会导致某些反向语义的文本仍有较高的相似度。
Bert提供了一个变体模型sentence-bert来专门生成句子的特征,计算句子的文本相似度。其用法举例如下:
from sentence_transformers import SentenceTransformer, util
# 加载Sentence-BERT模型
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
# 定义两个文本
text1 = "这个商品挺好用的"
text2 = "这个商品一点也不好用"
# 使用模型生成文本的embedding
embedding1 = model.encode(text1, convert_to_tensor=True)
embedding2 = model.encode(text2, convert_to_tensor=True)
# 计算两个文本的余弦相似度
similarity_score = util.pytorch_cos_sim(embedding1, embedding2)
# 输出相似度结果
print(f"文本相似度: {similarity_score.item():.4f}") #0.8351