BERT模型的输出格式探究以及提取出BERT 模型的CLS表示,last_hidden_state[:, 0, :]用于提取每个句子的CLS向量表示

news2025/1/16 16:58:53

说在前面

最近使用自己的数据集对bert-base-uncased进行了二次预训练,只使用了MLM任务,发现在加载训练好的模型进行输出CLS表示用于下游任务时,同一个句子的输出CLS表示都不一样,并且控制台输出以下警告信息。说是没有这些权重。

Some weights of BertModel were not initialized from the model checkpoint at ./model/test-model and are newly initialized: ['bert.pooler.dense.bias', 'bert.pooler.dense.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

BertModel 的某些权重在 ./model/test-model 的模型检查点时未被初始化,现在是新初始化的: ['bert.pooler.dense.bias','bert.pooler.dense.weight']。
您可能应该在下流任务中训练该模型,以便将其用于预测和推理。

问题原因

我刚开始获取CLS token表示使用的是以下方式,池化后的CLS token表示。

# 获取池化后的[CLS] token 表示
pooler_output = outputs.pooler_output 

但是我使用的模型是以下加载方式,它的输出并不包含 pooler_output,因为这个模型是为 掩码语言模型(Masked Language Model)任务设计的,而不是用于分类任务。因此,模型的输出包括 last_hidden_state,而不包括 pooler_output

model = AutoModelForMaskedLM.from_pretrained(BERT_PATH).to('cuda')
# 或者
model = BertForMaskedLM.from_pretrained(BERT_PATH).to('cuda')

所以我加载使用训练后的模型总是对同一个句子的CLS输出总是变化,就是没有pooler_output层的权重参数,它加载模型推理时自己随机初始化了pooler_output层权重参数。

解决方法

一共有两个解决方法。

1. 直接使用last_hidden_state[:,0,:]获取每个句子的cls token的表示。缺点:cls 表示在句子级表示方面差于pooler_output表示。

2. 修改训练的模型架构,添加池化层。pooler_output表示的优点是它对 [CLS] token 的表示进行了池化处理,它通常是更好的句子级别表示。

1. BERT模型的输出格式探究

last_hidden_state

last_hidden_state:这是一个张量,形状为 [batch_size, sequence_length, hidden_size]

  • batch_size:一次传入模型的样本数。
  • sequence_length:输入序列的长度(即输入文本中的 token 数量)。
  • hidden_size:每个 token 的隐藏状态向量的维度,通常是 768(BERT-base)或 1024(BERT-large)。

形象生动图形

真实示例

以下是1个batch_size的真实数据,每一行就是一个token,共有512行。每行里面的数值共有768个。该last_hidden_state 的形状是 [1, 512, 768]

last_hidden_state:  

tensor([[[-0.1043,  0.0966, -0.2970,  ..., -0.3728,  0.2120,  0.5492],
         [ 0.0131, -0.0778,  0.0908,  ..., -0.1869,  1.0111,  0.1027],
         [-0.8840,  0.3916,  0.3881,  ..., -0.5864,  0.3374,  0.1069],
         ...,
         [-0.2845, -0.8075,  0.6715,  ..., -0.5281,  0.5046, -0.6814],
         [-0.4623, -0.6836, -0.8556,  ...,  0.1499,  0.1142,  0.0486],
         [ 0.5701, -0.1264, -0.2348,  ...,  0.2635, -0.4314, -0.1724]]])

2. 获取每个句子的CLS token向量表示

last_hidden_state[:, 0, :]的含义=CLS向量表示

  • 表示我们选择了所有的批次样本。batch_size 为 1 时,选择所有样本,即 [1, 512, 768] 中的所有样本。
  • 0:表示我们选择了每个句子序列中的第一个 token(索引 0),在 BERT 中,输入序列的第一个 token 通常是 [CLS] token。因此,0 索引指向 [CLS] token 对应的隐藏状态。
  • 表示我们选择了所有的隐藏维度(即每个 token 的隐藏状态),也就是每个 token 的向量表示,通常为 768 维(对于 BERT-base)。

因此该操作的含义是:每个句子有512个token,提取每个句子里面的第1个token向量,人话说就是每个句子的的第1个token向量就是每个句子的CLS向量表示。

结果:这将会返回一个形状为 [1, 768] 的张量,它包含了 [CLS] token 的表示。由于 batch_size=1,最终的张量只有一个样本。

为什么last_hidden_state[:, 0, :]提取的就是每个句子的CLS token表示呢?

在 BERT 模型中,[CLS] token 是一个特殊的 token,通常用于表示整个句子的嵌入(embedding),特别是在分类任务中,[CLS] token 的输出被用作整个输入句子的向量表示。

  • 分类任务 中(如情感分析、文本分类等),通常使用 [CLS] token 的表示 作为整个句子的特征向量输入到分类器中。
  • 其他任务 中(如命名实体识别、问答系统等),[CLS] token 的表示也常常被用作输入的高层特征。

3. last_hidden_state 和 pooler_output的含义区别

outputs.last_hidden_stateoutputs.pooler_output 是 BERT 模型的两个重要输出,二者之间有明显的区别。它们分别代表了不同层级的模型输出,具体如下:

outputs.last_hidden_state

  • 定义last_hidden_state 是 BERT 模型中每一层的输出,包含了模型对于输入文本中每个 token 的隐藏表示。

  • 形状last_hidden_state 的形状通常是 [batch_size, sequence_length, hidden_size],即:

    • batch_size:批次中样本的数量。
    • sequence_length:输入序列(即文本)的长度,通常是 token 的个数(包括 [CLS][SEP] token)。
    • hidden_size:每个 token 的隐藏状态向量的维度(通常是 768,对于 bert-base-uncased)。
  • 用途last_hidden_state 是 BERT 对每个 token 的表示,包含了输入文本中每个 token 在其上下文中被表示出来的隐藏状态。它包含了完整的上下文信息。

    例如,对于输入文本 "Hello, how are you?",last_hidden_state 包含了 "Hello"",""how" 等每个 token 的上下文嵌入(表示)。你可以根据这个输出提取每个 token 的表示或使用 [CLS] token 的表示(last_hidden_state[:, 0, :])作为整个句子的表示。

outputs.pooler_output

  • 定义pooler_output 是一个经过额外处理的 [CLS] token 的表示。BERT 的 pooler 是一个简单的全连接层,它接收 last_hidden_state[CLS] token 的输出,然后对其进行处理(通常是通过一个 tanh 激活函数)以得到一个句子级别的特征表示。
  • 形状pooler_output 的形状通常是 [batch_size, hidden_size],即:
    • batch_size:批次中的样本数。
    • hidden_size:每个样本的 pooler_output 的维度(通常是 768)。
  • 用途pooler_output[CLS] token 的经过进一步处理后的表示,通常用于分类任务中。pooler_output 是通过对 last_hidden_state[CLS] token 的输出应用池化操作(通常是 tanh 激活函数)得到的最终句子级别的表示。这个表示通常用于下游任务,如分类任务。

两者的区别

  • last_hidden_state

    • 是 BERT 的每个 token 的 上下文表示。它是来自模型的 所有 token 的输出,形状为 [batch_size, sequence_length, hidden_size]
    • 它包含了对输入文本中每个 token 的隐藏状态表示,可以通过它提取每个 token(包括 [CLS])的表示。
  • pooler_output

    • 仅包含 [CLS] token 的表示,并且是经过池化处理(通过 tanh)后的结果,形状为 [batch_size, hidden_size]
    • 它通常用于 句子级别的表示,尤其在分类任务中,pooler_output 是更常见的输入特征。

何时使用哪一个?

  • last_hidden_state:如果你需要每个 token 的表示(如进行命名实体识别、文本生成等任务),你应该使用 last_hidden_state
    • 例如:对于文本分类任务中的 BERT 模型,你通常会使用 [CLS] token 的 last_hidden_state 来提取句子的表示,last_hidden_state[:, 0, :]
  • pooler_output:如果你只是进行 句子级别的分类任务(如情感分析、文本分类等),通常会直接使用 pooler_output,因为它已经对 [CLS] token 的表示进行了处理,通常是更好的句子级别表示。
    • 例如:对于情感分析,你会使用 pooler_output 作为整个句子的向量表示进行分类。

代码示例

假设你正在使用 BERT 模型进行文本分类,你可以使用以下代码来区分这两个输出:

import torch
from transformers import BertModel, BertTokenizer

# 加载模型和分词器
model = BertModel.from_pretrained("bert-base-uncased")
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# 输入文本
text = "Hello, how are you?"

# 编码文本
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)

# 获取模型的输出
with torch.no_grad():
    outputs = model(**inputs)

# 获取每个 token 的表示
last_hidden_state = outputs.last_hidden_state  # [batch_size, sequence_length, hidden_size]

# 获取[CLS] token 的表示(从 last_hidden_state 中提取)
cls_last_hidden = last_hidden_state[:, 0, :]  # [batch_size, hidden_size]

# 获取池化后的[CLS] token 表示
pooler_output = outputs.pooler_output  # [batch_size, hidden_size]

print("CLS token's last hidden state:", cls_last_hidden)
print("CLS token's pooler output:", pooler_output)

总结

  • last_hidden_state:包含了每个 token 的 上下文表示,你可以用它来获取每个 token 的隐藏状态(包括 [CLS])。
  • pooler_output:仅包含 [CLS] token 的表示,并且经过了池化处理,通常用于句子级别的任务,如文本分类。

4. 疑惑:pooler_output表示问题

我对bert进行了二次预训练后保存的模型输出发现并没有pooler_output权重参数(也就是文章开始处给出的警告信息),但是我又想要使用训练后的模型进行情感分析,我是直接使用lasthiddenstate的cls token表示呢,还是对其加一个池化处理呢?

cls_output = outputs.last_hidden_state[:, 0, :]  
print(cls_output)

pooler_output = outputs.pooler_output
print("pooler_output: ", pooler_output)

没有pooler_output权重参数的原因分析

因为我使用的是 BertForMaskedLM 模型,它的输出并不包含 pooler_output,因为这个模型是为 掩码语言模型(Masked Language Model)任务设计的,而不是用于分类任务。因此,模型的输出包括 last_hidden_state,而不包括 pooler_output

解决方案

1. 直接使用 last_hidden_state[:, 0, :]([CLS] token 的表示)

cls_representation = outputs.last_hidden_state[:, 0, :]

2. [CLS] token 的表示进行池化或全连接层处理

我觉得仅使用 CLS token 的输出还不够,可以加一个简单的全连接层(例如,用 tanh 激活函数)来进一步池化或优化 CLS token 的表示。

import torch.nn as nn

class SentimentAnalysisModel(nn.Module):
    def __init__(self, model):
        super(SentimentAnalysisModel, self).__init__()
        self.bert = model
        self.fc = nn.Linear(768, 2)  # 假设是二分类任务(情感分析)

    def forward(self, input_ids, attention_mask):
        outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
        cls_rep = outputs.last_hidden_state[:, 0, :]  # 获取 [CLS] token 的表示
        logits = self.fc(cls_rep)  # 使用全连接层进行分类
        return logits

# 使用训练后的模型来构建情感分析模型
sentiment_model = SentimentAnalysisModel(model)

在这个例子中,cls_rep[CLS] token 的表示,之后我们将其传入一个全连接层进行二分类(情感分析)。当然,如果你有更多类别,你可以根据任务需要修改输出层。

总结

  • 没有 pooler_output:由于我使用的是 BertForMaskedLM,该模型的输出没有 pooler_output,但你可以使用 last_hidden_state[:, 0, :](即 [CLS] token 的表示)作为句子级别的特征来进行情感分析。
  • 是否加池化:我觉得仅使用 [CLS] token 的表示不够好,可以加一个池化层或全连接层来进一步处理该表示,通常这种方式会提高分类性能。

因此,可以继续使用 last_hidden_state 中的 [CLS] token 作为情感分析的特征,如果需要提高模型效果,可以尝试在其基础上加一个池化层或全连接层。

5. 修改模型加载器方法探究

有没有其他解决方法呢,让我二次预训练后的模型有pooler_output。可以修改模型加载器吗,但是我使用的是MLM任务来二次预训练bert模型。我的训练任务是对bert模型进行二次预训练。

为了在训练后的 BERT 模型中有 pooler_output,需要稍作修改,因为默认的 BertForMaskedLM 模型并不包含 pooler_output(该输出通常用于分类任务的 BERT 模型,例如 BertForSequenceClassification)。因此,可以通过修改模型的架构来实现这一目标。

解决方案:修改模型架构

在加载模型时,选择使用 BertModel 或继承 BertForMaskedLM 的新模型,并手动添加一个池化层(如全连接层)。这种方法允许继续使用 MLM 任务,同时为分类任务提供所需的 pooler_output

修改代码的步骤

第一步:继承 BertForMaskedLM 并添加 pooler_output

  • 修改 BertForMaskedLM 模型,使其输出 pooler_output
  • 可以通过添加一个全连接层来模拟池化过程。
class BertWithPoolerOutput(BertForMaskedLM):
    def __init__(self, config):
        super().__init__(config)
        # 定义pooler层
        self.pooler = torch.nn.Linear(config.hidden_size, config.hidden_size)
        self.tanh = torch.nn.Tanh()
        # 使用BertModel来获得last_hidden_state
        self.bert = BertModel(config)

    def forward(self, input_ids=None, attention_mask=None, token_type_ids=None, labels=None):
        # 使用BertModel来获取last_hidden_state
        bert_outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask,
                                 token_type_ids=token_type_ids)
        last_hidden_state = bert_outputs.last_hidden_state  # 获取last_hidden_state

        # 提取[CLS]的输出
        cls_token_representation = last_hidden_state[:, 0, :]  # 获取[CLS] token的表示
        # 通过池化层(全连接层)进行处理
        pooler_output = self.tanh(self.pooler(cls_token_representation))

        # 获取BERT的MaskedLM输出
        lm_outputs = super().forward(input_ids=input_ids, attention_mask=attention_mask,
                                     token_type_ids=token_type_ids, labels=labels)

        # 返回字典,包含loss, logits, pooler_output
        return {
            'loss': lm_outputs.loss,
            'logits': lm_outputs.logits,
            'pooler_output': pooler_output
        }

在这个新类 BertWithPoolerOutput 中,我们继承了 BertForMaskedLM,并增加了一个池化层 (self.pooler) 和激活函数(tanh),用于生成 pooler_output。此修改确保在进行二次预训练时仍然可以使用池化后的表示。为了正确获取 last_hidden_state,我们应该调用 BertModel 来获取 last_hidden_state,而不是直接从 BertForMaskedLM 获取。

第2步:加载并训练这个模型

通过这种方式,加载并训练模型时,模型将返回 pooler_output,就可以使用它进行情感分析或其他分类任务。

from transformers import BertTokenizer, Trainer, TrainingArguments
from datasets import load_dataset
from transformers import DataCollatorForLanguageModeling

# 训练和数据集代码保持不变,只是模型加载部分更换为我们自定义的模型
tokenizer = BertTokenizer.from_pretrained(BERT_PATH)
model = BertWithPoolerOutput.from_pretrained(BERT_PATH)  # 使用我们自定义的模型

# 其余的训练部分和数据处理代码不变

第三步:在训练过程中使用 pooler_output

训练结束后,模型将返回 pooler_output,你可以直接使用它进行分类任务。

# 假设输出为 `outputs`,你可以访问 `pooler_output`
pooler_output = outputs.pooler_output

总结

  • 可以通过 自定义模型 来为 BertForMaskedLM 添加 pooler_output,方法是在原有的 BertForMaskedLM 上增加一个池化层。
  • 这样,模型仍然可以用于 MLM 任务,同时也能输出 pooler_output,而它通常是更好的句子级别表示,便于后续情感分析等任务。
  • 训练完成后,就能使用 pooler_output,它是通过池化 [CLS] token 的表示得到的句子级别的特征。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2256553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LSTM的A股股票价格预测系统(torch) :从数据获取到模型训练的完整实现

1. 项目简介 本文介绍了一个使用LSTM(长短期记忆网络)进行股票价格预测的完整系统。该系统使用Python实现,集成了数据获取、预处理、模型训练和预测等功能。 这个代码使用的是 LSTM (Long Short-Term Memory) 模型,这是一种特殊的…

【ArcGIS微课1000例】0134:ArcGIS Earth实现二维建筑物的三维完美显示

文章目录 一、加载数据二、三维显示三、三维符号化一、加载数据 加载配套实验数据(0134.rar中的建筑物,2d或3d都可以),方法如下:点击添加按钮。 点击【Add Files】,在弹出的Open对话框中,选择建筑物,点击确定,完成添加。 默认二维显示: 二、三维显示 右键建筑物图层…

汽车EEA架构:发展历程

1.发展历程的基本逻辑 汽车电子电气的发展历程中,其使用的基本逻辑是IPO(Input-Processing-Output)模型,如下图1所示: 图 1 那什么是IPO模型了?我们从控制器的原理入手解释IPO模型,控制器的主要用途如下: 根据给定的逻…

python拆分Excel文件

按Sheet拆分Excel 或 按照某一列的不同值拆分Excel。文档样式如下: 结果:红色是按照Sheet名拆出的,蓝色和橙色是某个Sheet按照某列的不同值拆分的。 代码: # -*- coding: utf-8 -*- """ 拆分excel文件——按照…

存内架构IR-DROP问题详解-电容电导补偿

一、总述 电容、电导补偿作为大规模数字电路的关键设计理念,是 CIM 架构优化的核心技术。在 CIM 中,平衡电容或电导并实现计算的精准映射,对能效提升和计算精度保障具有关键作用。本文基于近期文献探讨电容、电导补偿在 CIM 中的具体补偿策…

汽车网络安全 -- IDPS如何帮助OEM保证车辆全生命周期的信息安全

目录 1.强标的另一层解读 2.什么是IDPS 2.1 IDPS技术要点 2.2 车辆IDPS系统示例 3.车辆纵深防御架构 4.小结 1.强标的另一层解读 在最近发布的国家汽车安全强标《GB 44495》,在7.2节明确提出了12条关于通信安全的要求,分别涉及到车辆与车辆制造商云平台通信、车辆与车辆…

【数字化】华为企业数字化转型-认知篇

导读:企业数字化转型的必要性在于,它能够帮助企业适应数字化时代的需求,提升运营效率,创新业务模式,增强客户互动,从而在激烈的市场竞争中保持领先地位并实现可持续发展。通过学习华为企业数字化转型相关理…

用C#开发程序进行ASCII艺术制作

我一直很喜欢 ASCII 艺术,而我对制作 ASCII 艺术的热情促使我探索 .NET 框架中的 GDI。在本文中, 我将向您展示如何通过三个简单的步骤从 JPEG/Bitmap 图像生成 ASCII 艺术。 1、加载并调整图像大小。 2、读取每个像素,获取其颜色并将其转换…

第23周:机器学习及文献阅读

目录 摘要 Abstract 一、理论知识 1、逻辑提升 2、分类任务 3、10倍交叉验证法 二、文献阅读 1、模型方法——MLT (1)特征选择 (2)决策树剪枝 2、分类任务——逻辑回归 3、实验部分 数据集的选取 代码实践 模型…

2020年国赛高教杯数学建模E题校园供水系统智能管理解题全过程文档及程序

2020年国赛高教杯数学建模 E题 校园供水系统智能管理 原题再现 校园供水系统是校园公用设施的重要组成部分,学校为了保障校园供水系统的正常运行需要投入大量的人力、物力和财力。随着科学技术的发展,校园内已经普遍使用了智能水表,从而可以…

React开发高级篇 - React Hooks以及自定义Hooks实现思路

Hooks介绍 Hooks是react16.8以后新增的钩子API; 目的:增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。 为什么要使用Hooks? 开发友好,可扩展性强&#…

摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/12/03

Learning Roadmap: Section 1: Intro to Parallel Programming & MUSA Deep Learning Ecosystem(摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/11/30-CSDN博客)UbuntuDriverToolkitcondapytorchtorch_musa环境安装(2024/11/24-Ubunt…

如何使用Docker轻松搭建高颜值无广告音乐播放器SPlayer随时随地听歌

前言 在快节奏的生活环境中,音乐成为了许多人放松和享受的重要方式。本文将介绍如何在Linux Ubuntu系统中使用Docker快速部署一款高颜值无广告的某抑云音乐播放器——SPlayer,并结合Cpolar内网穿透工具实现出门在外也能远程访问本地服务,随时…

C# Decimal

文章目录 前言1. Decimal 的基本特性2. 基本用法示例3. 特殊值与转换4. 数学运算示例5. 精度处理示例6. 比较操作示例7. 货币计算示例8. Decimal 的保留小数位数9. 处理 Decimal 的溢出和下溢10. 避免浮点数计算误差总结 前言 decimal 是 C# 中一种用于表示高精度十进制数的关键…

【理论·专业课】第三次作业

第1题(存储管理_内存碎片) 请指出内部碎片与外部碎片的区别。 ANS: 内部碎片是分配给进程但未被进程使用且无法被其他进程利用的内存空间 外部碎片是内存中因进程分配释放内存形成的不连续小块,虽总和够但因不连续无…

最新的springboot 3.x的支持s3协议的2.x方法的minio上传文件方法

拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/minio:latest运行命令 docker run -d \--name minio \-p 10087:9000 \-p 10088:9001 \-e MINIO_ROOT_USERminioadmin \-e MINIO_ROOT_PASSWORDY6HYraaphfZ9k8Lv \-v /data/minio/data:/data \-v /data/…

cocos creator接入字节跳动抖音小游戏JSAPI敏感词检测(进行文字输入,但输入敏感词后没有替换为*号)

今天更新了某个抖音小游戏的版本,增加了部分剧情,半天过后一条短信审核未通过,emmm…抖音总是能给开发者惊喜…打开电脑看看这次又整什么幺蛾子… 首先是一脸懵逼,后端早已接入了官方的内容安全检测能力了(https://de…

Origin快速拟合荧光寿命、PL Decay (TRPL)数据分析处理-方法二

1.先导入数据到origin 2.导入文件的时候注意:名字短的这个是,或者你打开后看哪个里面有800,因为我的激光重频是1.25Hz(应该是,不太确定单位是KHz还是MHz),所以对应的时间是800s。 3.选中两列直接…

17. 面向对象的特征

一、面向对象的三大特征 面向对象的三大特征指的是 封装、继承、多态。 封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的…

Apache Dolphinscheduler可视化 DAG 工作流任务调度系统

Apache Dolphinscheduler 关于 一个分布式易扩展的可视化 DAG 工作流任务调度系统。致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开箱即用。 DolphinScheduler 的主要特性如下: 易于部署,提供四种部署方式&am…