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

说在前面

最近使用自己的数据集对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 nnclass SentimentAnalysisModel(nn.Module):def __init__(self, model):super(SentimentAnalysisModel, self).__init__()self.bert = modelself.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_stateself.bert = BertModel(config)def forward(self, input_ids=None, attention_mask=None, token_type_ids=None, labels=None):# 使用BertModel来获取last_hidden_statebert_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_outputreturn {'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.rhkb.cn/news/484633.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式:18、组合模式

目录 0、定义 1、组合模式的三种角色 2、组合模式的UML类图 3、示例代码 0、定义 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使用户对单个对象和组合对象的使用具有一致性。 1、组合模式的三种角色 抽象组件(Component)&#…

Canal 深入解析:从原理到实践的全面解读

Canal 深入解析:从原理到实践的全面解读 官网:https://github.com/alibaba/canal Canal 是阿里巴巴开源的一款分布式增量数据同步工具,广泛应用于数据同步、实时数据处理和数据库的增量备份等场景。它可以通过监听 MySQL 数据库的 binlog&am…

LCD1602液晶显示屏指令详解

文章目录 LCD1602液晶显示屏1.简介2. 液晶引脚说明3. 指令介绍3.1 清屏指令3.2 光标归位指令3.3 进入模式设置指令3.4 显示开关设置指令3.5 设定显示或光标移动方向指令3.6 功能设定指令3.7 设定CGRAM地址指令3.8 设定DDRAM地址指令3.9 读取忙或AC地址指令3.10 总图3.11 DDRAM …

阿里云中Flink提交作业

访问阿里云首页面:https://www.aliyun.com/ 选择"按量付费" 通过选择区域,看哪个区域有虚拟交换机。 查看创建的工作空间,当工作空间状态为运行中时,点击控制台。 开通完成后,会有一个控制台: 可…

【不稳定的BUG】__scrt_is_managed_app()中断

【不稳定的BUG】__scrt_is_managed_app函数中断 参考问题详细的情况临时解决方案 参考 发现出现同样问题的文章: 代码运行完所有功能,仍然会中断 问题详细的情况 if (!__scrt_is_managed_app())exit(main_result);这里触发了一个断点很奇怪,这中断就发生了一次,代…

JDK 24:Java 24 中的新功能

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:历代文学,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计&#xf…

联想按下“AI加速键”!目标:与5000万中小企业共创

根据相关数据显示,截至2023年末中国中小企业数量超过5300万家,中小企业支撑了中国经济的发展与前进。在AI大模型风潮到来之际,相比于AI带给大企业的长期价值,AI对中小企业有着更加直接、显著、决定性的意义。同时,AI与…

【React】二、状态变量useState

文章目录 1、React中的事件绑定1.1 基础事件绑定1.2 使用事件对象参数1.3 传递自定义参数1.4 同时传递事件对象和自定义参数 2、React中的组件3、useState 1、React中的事件绑定 1.1 基础事件绑定 语法:on 事件名称 { 事件处理程序 },整体上遵循驼峰…

计算机网络-IPSec VPN基本概念

企业分支之间经常有互联的需求,企业互联的方式很多,可以使用专线线路或者Internet线路。部分企业从成本和需求出发会选择使用Internet线路进行互联,但是使用Internet线路存在安全风险,如何保障数据在传输时不会被窃取?…

VirtualBox注册已有虚拟机:未能打开位于虚拟电脑E_INVALIDARG (0X80070057)

错误如下 解决办法1 产生虚拟机的机器,与当前使用机器不兼容。建议在当前机器重新产生虚拟机。比如我家里电脑是WIN7,公司电脑是WIN11。 原来的虚拟机内容,找老机器导出。 解决办法2(存疑) 搜索到一个说法&#xf…

浅谈网络 | 应用层之流媒体与P2P协议

目录 流媒体名词系列视频的本质视频压缩编码过程如何在直播中看到帅哥美女?RTMP 协议 P2PP2P 文件下载种子文件 (.torrent)去中心化网络(DHT)哈希值与 DHT 网络DHT 网络是如何查找 流媒体 直播系统组成与协议 近几年直播比较火,…

2023年第十四届蓝桥杯Scratch国赛真题—推箱子

推箱子 程序演示及其源码解析,可前往: https://www.hixinao.com/scratch/creation/show-188.html 若需在线编程,在线测评模考,助力赛事可自行前往题库中心,按需查找: https://www.hixinao.com/ 题库涵盖…

【学习总结|DAY012】Java面向对象基础

一、前言 今天主要学习了以下内容:面向对象的理解与使用、对象的内存布局、构造器的概念和作用、封装的重要性以及JavaBean实体类的实现等。下面我将详细阐述这些知识点。 二、面向对象的理解与使用 1. 什么是面向对象? 类:一种特殊的数据…

网络安全知识:网络安全网格架构

在数字化转型的主导下,大多数组织利用多云或混合环境,包括本地基础设施、云服务和应用程序以及第三方实体,以及在网络中运行的用户和设备身份。在这种情况下,保护组织资产免受威胁涉及实现一个统一的框架,该框架根据组…

Spring04——注解开发

Spring3.0启用了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道 Java类代替Spring核心配置文件, 配置类(Configuration) Configuration注解用于设定当前类为配置类ComponentScan注解用于设定扫描路…

01_Node.js入门 (黑马)

01_Node.js入门 知识点自测 从 index.js 出发&#xff0c;访问到 student/data.json 的相对路径如何写? A&#xff1a;../public/teacher/data.json B&#xff1a;./public/student/data.json C&#xff1a;../student/data.json <details><summary>答案</sum…

哪款云手机适合多开?常用云手机功能对比

在全球化和数字化时代&#xff0c;云手机以其独特的灵活性和高效性&#xff0c;成为多账号运营和数字营销的热门工具。云手机能够解决传统设备管理的诸多痛点&#xff0c;例如账号关联、硬件成本高等问题。本文将为您推荐多款优质云手机品牌&#xff0c;帮助您选择最适合的工具…

【目标跟踪】DUT Anti-UAV数据集详细介绍

DUT Anti-UAV数据集是大连理工大学的团队公开的数据集&#xff08;DUT是他们学校的简称&#xff09;&#xff0c;其中包括了两个子数据集&#xff1a;目标检测和目标跟踪&#xff08;也就是说&#xff0c;目标检测和目标跟踪都可以用这个数据集&#xff09;。该数据集为可见光模…

青岛鼎信Java开发面试题及参考答案

MySQL 的事务特性有哪些&#xff1f; MySQL 事务具有四个重要特性&#xff0c;被称为 ACID 特性。 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是一个不可分割的工作单位&#xff0c;事务中的操作要么全部执行&#xff0c;要么全部不执行。例如&#xff0c;在银行…

【机器学习】分类器

在机器学习(Machine Learning&#xff0c;ML)中&#xff0c;分类器泛指算法或模型&#xff0c;用于将输入数据分为不同的类别或标签。分类器是监督学习的一部分&#xff0c;它依据已知的数据集中的特征和标签进行训练&#xff0c;并根据这些学习到的知识对新的未标记数据进行分…