北京网站建设方案建设公司,扬中潘杰简历,网站备案备注信息,seo专员是干什么的说在前面
最近使用自己的数据集对bert-base-uncased进行了二次预训练#xff0c;只使用了MLM任务#xff0c;发现在加载训练好的模型进行输出CLS表示用于下游任务时#xff0c;同一个句子的输出CLS表示都不一样#xff0c;并且控制台输出以下警告信息。说是没有这些权重。…说在前面
最近使用自己的数据集对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 的隐藏状态向量的维度通常是 768BERT-base或 1024BERT-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_size1最终的张量只有一个样本。 为什么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_state 和 outputs.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_tensorspt, truncationTrue, paddingTrue)# 获取模型的输出
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 tokens last hidden state:, cls_last_hidden)
print(CLS tokens 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_idsinput_ids, attention_maskattention_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_idsNone, attention_maskNone, token_type_idsNone, labelsNone):# 使用BertModel来获取last_hidden_statebert_outputs self.bert(input_idsinput_ids, attention_maskattention_mask,token_type_idstoken_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_idsinput_ids, attention_maskattention_mask,token_type_idstoken_type_ids, labelslabels)# 返回字典包含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 的表示得到的句子级别的特征。