NLP:BERT的介绍并使用该模型计算文本相似度

1 Transformer

  Transformer架构是一种基于自注意力机制(self-attention)的神经网络架构,它代替了以前流行的循环神经网络和长短期记忆网络,已经应用到多个自然语言处理方向。
  Transformer架构由两个主要部分组成:编码器(Encoder)和解码器(Decoder)。编码器和解码器均是由多个层(layer)堆叠而成,其中每层均由多个子层组成:比如自注意力机制和前馈神经网络。(本篇先不介绍解码器部分。)
在这里插入图片描述

1.1 编码器

  Transformer中的编码器的作用是提取原句中的特征值。Transformer的编码器不止一个,而是由一组 N N N个编码器串联而成。一个编码器的输出作为下一个编码器的输入。编码器由两部分组成:多头注意力层和前馈网络层。

1.1.1 多头注意力层

  要理解Transformer的多头注意力层,就必须先理解Transformer中的自注意力机制(self-attention)。Transformer中的自注意力机制一种能够使模型在处理序列数据时,通过计算序列中每个元素与其他所有元素之间的相关性,并据此对元素进行加权求和,从而生成包含所有元素信息但更侧重于重要部分的表示的机制。多头注意力机制就是自注意力机制的扩展,它通过并行计算多个自注意力头来捕捉不同子空间中的信息,最终将这些头的输出进行拼接和线性变换。
  自注意力机制的计算过程如下图。其中 Q Q Q为查询矩阵、 K K K为键矩阵、 V V V为值矩阵。
在这里插入图片描述

1.1.2 位置编码

  Transformer中的位置编码用于为输入序列中的每个词提供位置信息,以弥补模型中缺少顺序感的缺陷,使模型能够捕捉词汇的相对顺序和位置信息。

1.1.3 前馈网络层

  Transformer架构中的前馈网络由两个有ReLU激活函数的全连接层组成。前馈网络的参数在句子的不同位置上是相同的,但在不同的编码器模块上是不同的。

1.1.4 叠加和归一化组件

  叠加和归一组件实际上包含一个残差连接与层的归一化。层的归一化可以防止每层的值剧烈变化,从而提高了模型的训练速度。

至此,完整的编码器框架如下:
在这里插入图片描述

2 BERT模型

  BERT(Bidirectional Encoder Representations from Transformers,多Transformer的双向编码器表示法)模型是由谷歌发布的预训练语言模型。

2.1 Bert模型预训练

  Bert模型在一个巨大的语料库上针对两种特定任务进行预训练,这两种任务是掩码语言模型构建和下句预测。Bert在训练这两类任务时,其数据输入过程是一样的,只输出层的设计和任务逻辑上存在差异。

2.1.1 输入过程

  输入层将文本转换为BERT能够处理的形式,主要包括以下三个部分:

  • Token Embeddings: 将输入的每个词或子词(通过WordPiece分词)映射为对应的词向量;在BERT预训练开始时,嵌入表中的向量通常是随机初始化的。常见的初始化方法包括Xavier初始化或正态分布初始化,这种随机初始化为模型提供了初始权重。
  • Segment Embeddings:会分别给第一个句子的所有Token都分配0作为ID,用来标记它们属于第一个句子。给第二个句子的所有Token都分配1作为ID,用来标记它们属于第二个句子。
  • Position Embeddings:因为BERT不使用传统的RNN或CNN结构,而是基于自注意力机制,所以需要显式添加位置编码,表示词的相对位置,帮助模型捕捉词序信息。
    在这里插入图片描述
2.1.2 预训练任务

不同的预训练任务对应不同的输出过程。具体介绍如下:

  • 掩码语言模型构建(Masked Language Model, MLM): MLM的目标是预测输入序列中被随机掩盖的词。模型通过上下文信息来推断这些被掩盖的单词。这个过程让模型在预训练中能够学习词与词之间的双向依赖关系,而不仅仅是像传统的单向语言模型那样只考虑前向或后向依赖。
    在MLM任务中,输出层的结构通常是一个softmax层,其输入是BERT编码器的最后一层输出(每个token的上下文表示)。这个输出层的维度与词汇表的大小相同,模型会为每个被掩盖的词计算概率分布,从中选择最可能的词。
  • 下句预测(Next Sentence Prediction, NSP): NSP任务的目标是让模型学习句子级别的关系,特别是判断两个句子是否连续。这种任务帮助BERT更好地理解句子之间的上下文关系,对于许多需要句子关系的任务(如问答、自然语言推理)有很大的帮助。
    在NSP任务中,输出层通常是一个二分类的softmax层。这个输出层的输入通常是BERT的[CLS]token的表示。通过这个输出层,模型会输出两个类别的概率(即“是下一句”或“不是下一句”)。

2.2 Bert微调

2.2.1 常用的Bert预训练模型

  谷歌对外公开了其预训练的BERT模型,用户可以直接下载使用。其下载地址如下:https://huggingface.co/google-bert
在这里插入图片描述
Tips: BERT模型名称中的的uncased表示不区分大小写,cased表示区分大小写。在不区分大小写时,所有标记都转化为小写;在区分大小写时,标记大小写不变,直接用于训练。不区分大小写的模型是最常用的模型,但如果我们正在执行某些任务,比如命名实体识别(named entity recognition, NER),则必须保留大小写,使用区分大小写的模型。

2.2.2 微调

在完成自监督的预训练之后,BERT会生成通用的语言表示,这些表示可以用于多种下游任务。为了适应具体任务,BERT模型会在每个下游任务的数据上进行微调。这一步通常包括:

  • 取BERT预训练模型的权重。
  • 在特定任务的数据集上训练(例如文本分类、命名实体识别、机器翻译、问答系统等)。
  • 根据具体任务的要求添加任务相关的输出层,如分类层或回归层。

3 BERT的使用

  在这里仅介绍如何利用预训练的Bert模型提取词嵌入的方法。其他微调案例会在其他博客中介绍。

3.1 从预训练的BERT模型中提取嵌入

  接下来将介绍如何使用Hugging Face上的Transformers库来提取每个输入句子的嵌入。具体代码如下:

from transformers import BertTokenizer, BertModel
import torch
#加载预训练模型
model=BertModel.from_pretrained('bert-base-chinese',output_hidden_states=True)
tokenizer=BertTokenizer.from_pretrained('bert-base-chinese')
#输入文本
sententce='今天天气不错'
#将文本转换为词元
tokens=tokenizer.tokenize(sententce)
print('tokens:',tokens)
#添加特殊符号:[CLS]和[SEP]
tokens=['[CLS]']+tokens+['[SEP]']
#假设我们需要的最大长度为10。如果tokens的长度小于10,则用[PAD]补齐
if len(tokens)<10:
    tokens=tokens+['[PAD]' for _ in range(10-len(tokens))]
print('tokens:',tokens)
#创建掩码向量
attention_mask=[1 if token!='[PAD]' else 0 for token in tokens]
#将所有token转换为词元id
tokens_id=tokenizer.convert_tokens_to_ids(tokens)
print('tokens_id:',tokens_id)
#将词元id、attention_mask转换为张量
tokens_id=torch.tensor(tokens_id).unsqueeze(0)
attention_mask=torch.tensor(attention_mask).unsqueeze(0)
#获取文本的BERT输出
output=model(tokens_id,attention_mask)
print(output.last_hidden_state.shape)
print(output.hidden_states.shape)
print(output.pooler_output.shape)

output中包含三种变量,具体如下:

  • last_hidden_state: 包含从最后的编码器中获得的所有标记的特征;
  • pooler_output表示来自最后的编码器的[CLS]标记的特征;
  • hidden_states包含从所有编码器层获得的所有标记的特征;

3.2 计算文本相似度

   通过使用 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 在大量通用语料上进行预训练,未必对所有语境中的细微差异或否定词有足够的敏感性,这会导致某些反向语义的文本仍有较高的相似度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值