大模型学习:从零到一实现一个BERT微调

目录

一、准备阶段

1.导入模块

2.指定使用的是GPU还是CPU

3.加载数据集

二、对数据添加词元和分词

1.根据BERT的预训练,我们要将一个句子的句头添加[CLS]句尾添加[SEP]

2.激活BERT词元分析器

3.填充句子为固定长度

代码解释:

三、数据处理

1.创建masks掩码矩阵

代码解释:

2.拆分数据集

3.将所有的数据转换为torch张量

4.选择批量大小并创建迭代器

代码解释:

四、BERT模型配置

1.初始化一个不区分大小写的 BERT 配置:

代码解释:

2.这些配置参数的作用:

3.加载模型

4.优化器分组参数

代码解释:

5.训练循环的超参数

代码解释:

五、训练循环

代码解释:

训练图解:

 六、使用测试数据集进行预测和评估

七、使用马修斯相关系数(MCC)评估

2. 代码实现:

1. 测试数据预处理与预测

2.模型预测与结果收集

3.计算MCC

到这里就完美收官咯!!!!! 大家点个赞吧!!!


本章将微调一个BERT模型来预测下游的可接受性判断任务,如果你的电脑还没有配置相关环境的可以去使用 Colaboratory - Colab,里面已经全部帮你配置好啦!而且还可以免费使用GPU。

一、准备阶段

1.导入模块

导入所需的预训练相关模块,包括用于词元化的 BertTokenizer、用于配置 BERT 模型的 BertConfig,还有 Adam 优化器(AdamW),以及序列分类模块(BertFo SequenceClassification):

import torch
import torch.nn as nn
from torch.utils.data import TensorDataset,DataLoader,RandomSampler,SequentialSampler
from sklearn.model_selection import train_test_split
from transformers import BertTokenizer,BertConfig
from transformers import BertForSequenceClassification,get_linear_schedule_with_warmup
# from transformers import AdamW
from torch.optim import AdamW
from tqdm import tqdm,trange
import pandas as pd
import io
import numpy as np
import matplotlib.pyplot as plt# 导入进度条
from tqdm import tqdm,trange
# 导入常用的标注python模块
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

2.指定使用的是GPU还是CPU

使用GPU加速对我们的训练非常又有帮助

device = torch.device("cuda" if torch.cuda.is_avsilable() else "cpu")

3.加载数据集

这里的代码和数据是参考https://github.com/Denis2054/Transformers-for-NLP-2nd-Edition/tree/main/Chapter03

使用git仓库拉取一下就可以得到数据了

df=pd.read_csv("你拉取的数据in_domain_train.tsv的路径",delimiter='\t',header=None,names=['sentence_source','label','label_notes','sentence'])
# 展示数据维度
df.shape  # (8551,4)

随机抽取十个样本数据的看看:

df.sample(10)

 以看到数据集中的数据包含了以下四列(即.tsv文件中四个用制表符分隔的列)。

● 第1列:句子来源(用编号表示)

● 第2列:标注(0=不可接受,1= 可接受)

● 第3列:作者的标注

● 第4列;要分类的句子

二、对数据添加词元和分词

1.根据BERT的预训练,我们要将一个句子的句头添加[CLS]句尾添加[SEP]

代码解释:将数据中的需要分类的句子提取为sentences,循环出每个句子,在每个句子的句头添加[CLS]句尾添加[SEP],将数据中的标签提取为labels

sentences=df.sentence.values
sentences=["[CLS]"+ sentence+"[SEP]" for sentence in sentences]
labels=df.label.values

2.激活BERT词元分析器

这里是初始化一个预训练BERT词元分析器。相比与从头开始训练一个词元分析器相对,节省很多时间和资源。们选择了一个不区分大小写的词元分析器,激活它,并展示对第一个句子词元 化之后的结果:

代码讲解:tokenizer是我们初始化的词元分析器,BertTokenizer.from_pretrained('bert-base-uncased')是使用BERT中自带的预训练好的参数,关于BertTokenizer.from_pretrained可以去看我的另外一章博客BertTokenizer.from_pretreined。

        tokenizer_texts是已经每个句子词元分析好的一个迭代器,因为sentences中保存的句子的type为array类型,而tokenize中要传入的是字符串类型,所以这里要强转一下

词元分析后的一条句子为:Tokenize the first sentence: ['[CLS]', 'our', 'friends', 'wo', 'n', "'", 't', 'buy', 'this', 'analysis', ',', 'let', 'alone', 'the', 'next', 'one', 'we', 'propose', '.', '[SEP]']

tokenizer=BertTokenizer.from_pretrained('bert-base-uncased')
tokenizer_texts=[tokenizer.tokenize(str(sent)) for sent in sentences]
print("Tokenize the first sentence: ")
print(tokenizer_texts[0])
# Tokenize the first sentence: 
['[CLS]', 'our', 'friends', 'wo', 'n', "'", 't', 'buy', 'this', 'analysis', ',', 'let', 'alone', 'the', 'next', 'one', 'we', 'propose', '.', '[SEP]']

3.填充句子为固定长度

上面的处理中我们不难想到,每个句子分析后的长度会随句子的大小而改变,而在BERT微调的时候需要保证句子的长度应用,所以我们要将长度不够的句子进行填充,我们将这个最大长度设置为128,对于长度超过128的数据我们将它截断,保证每个句子序列的大小都为128

代码解释:

input_ids:里面保存的是将上面的句子分词后的词元列表转化成对应数字的列表,其中将词元一个个的循环出来后经过tokenizer.convert_tokens_to_ids,它能把分词后的词元转化为整数 ID,从而让深度学习模型能够处理文本数据。在不同的库中,其使用方式可能会有所不同,但核心功能是一致的。

第二个input_ids:保存的是将每个词元序列转化成相同大小后的迭代器,pad_sequences函数的主要作用是将多个序列填充或截断至相同的长度,这在处理序列数据(像文本序列)时十分关键,因为神经网络通常要求输入数据具有统一的形状

from tensorflow.keras.preprocessing.sequence import pad_sequences
MAX_LEN=128
input_ids=[tokenizer.convert_tokens_to_ids(x) for x in tokenizer_texts]
print(input_ids[0])
input_ids=pad_sequences(input_ids,maxlen=MAX_LEN,dtype='long',truncating='post',padding='post')
print(input_ids[0])

三、数据处理

1.创建masks掩码矩阵

如果不知道为什么需要掩码的可以去看看transformers架构

为了防止模型对填充词元进行注意力计算,我们在前面的步骤中对序列进行了填充补齐。但是我们希望防止模型对这些填充 的词元进行注意力计算!首先创建一个空的 attention_masks 列表,用于存储每个序列的注意力掩码。然后, 对于输入序列(input_ids)中的每个序列(seq),我们遍历其中的每个词元。

针对每个词元,我们判断其索引是否大于 0。如果大于 0,则将对应位置的掩码 值设置为1,表示该词元是有效词元。如果等于0,则将对应位置的掩码值设置为0, 表示该词元是填充词元。最终得到的 attention_masks 列表中的每个元素都是一个与对应输入序列长度相同的 列表,其中每个位置的掩码值表示该位置的词元是否有效(1表示有效,0表示填充)。

通过使用注意力掩码,可确保在模型的注意力计算中,只有真实的词元会被考虑, 而填充词元则被忽略。这样可提高计算效率,并减少模型学习无用信息的概率。

代码解释:

attention_masks是保存所有词元掩码的列表,上面我们说到input_ids保存的是将每个词元序列转化成相同大小后的迭代器,我们将里面的每个词元序列遍历为seq,判断seq中的值是否大于0,如果大于0,那么它是有效词元,将他对应的掩码设置为1,反正为0

attention_masks=[]
for seq in input_ids:seq_mask=[float(i>0) for i in seq]attention_masks.append(seq_mask)
print(attention_masks[0])
"""[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]"""

2.拆分数据集

将数据拆分成训练集和验证集,训练集和测试集的比例为9:1

# 拆分训练集和验证集 (90%训练,10%验证)
train_inputs, val_inputs, train_labels, val_labels = train_test_split(input_ids, labels, test_size=0.1, random_state=2025)
train_masks, val_masks, _, _ = train_test_split(attention_masks, labels, test_size=0.1, random_state=2025)  

3.将所有的数据转换为torch张量

微调模型需要使用 torch 张量,所以我们需要将数据转换为 torch 张量:

train_inputs = torch.tensor(train_inputs)validation_inputs = torch.tensor(validation_inputs)txain_labels=torch.tensor(train_labels)validation_labels = torch.tensor(validation_labels)train_masks = torch.tensor(train_masks)validation_masks = torch.tensor(validation_masks)

4.选择批量大小并创建迭代器

如果一股脑地将所有数据都喂进机器,会导致机器因为内存不足而崩溃。所以需 要将数据一批一批地喂给机器。这里将把批量大小(batch size)设置为 32 并创建迭代 器。然后将迭代器与 torch的 DataLoader 相结合,以批量训练大量数据集,以免导致 机器因为内存不足而崩溃:

代码解释:

这里我们选的批量大小(batch_size)为32,使用TensorDataset和DataLoader创建训练数据迭代器,如果对TensorDataset和DataLoader不清楚的可以去看看我的另外一篇博客:TensorData和DataLoader

RandomSampler 是一种随机采样器,从给定的数据集中随机抽取样本,可选择有放回或无放回采样。无放回采样时,从打乱的数据集里抽取样本;有放回采样时,可指定抽取的样本数量 num_samples

batch_size=32
# 训练数据迭代器
train_data=TensorDataset(train_inputs,train_masks,train_labels)
train_sampler=RandomSampler(train_data)
train_dataloader=DataLoader(train_data,sampler=train_sampler,batch_size=batch_size)
# 测试数据迭代器validation_data=TensorDataset(validation_inputs,validation_masks,validation_label)
validation_sampler=RandomSampler(train_data)
validation_dataloader=DataLoader(validation_data,sampler=validation_sampler,batch_size=batch_size)

四、BERT模型配置

1.初始化一个不区分大小写的 BERT 配置:

代码解释:

后面的代码寻妖用到transformers这个包,如果没有的pip安装一下。

configuration是初始化了一个包含BERT预训练模型中所有超参数的配置实例,如果BertConfig中不加任何的参数,那么会生成一个标准的BERT-base配置:

{"hidden_size": 768,          # 每个Transformer层的维度"num_hidden_layers": 12,     # Transformer层数(深度)"num_attention_heads": 12,    # 注意力头的数量"intermediate_size": 3072,   # FeedForward层的中间维度"vocab_size": 30522,         # 词表大小(需与预训练模型一致)"max_position_embeddings": 512, # 最大序列长度...
}

 model:BertModel是根据配置生成一个随机初始化权重的BERT模型,根据传入的配置信息生成。

configuration是一个保存模型内部存储的配置信息副本

try:import transformers
except:print("installing transformers")
from transformers import BertModel,BertConfig
configuration=BertConfig()model=BertModel(config=configuration)
configuration=model.config
print(configuration)
"""
输出为:
BertConfig {"_attn_implementation_autoset": true,"attention_probs_dropout_prob": 0.1,"classifier_dropout": null,"hidden_act": "gelu","hidden_dropout_prob": 0.1,"hidden_size": 768,"initializer_range": 0.02,"intermediate_size": 3072,"layer_norm_eps": 1e-12,"max_position_embeddings": 512,"model_type": "bert","num_attention_heads": 12,"num_hidden_layers": 12,"pad_token_id": 0,"position_embedding_type": "absolute","transformers_version": "4.50.0","type_vocab_size": 2,"use_cache": true,"vocab_size": 30522
}
"""

2.这些配置参数的作用:

"""

attention probs_dropout_prob:对注意力概率应用的 dropout 率,这里设置为

0.1。

● hidden_act;编码器中的非线性激活函数,这里使用 gelu。gelu 是高斯误差线

性单位(Gaussian Eror Linear Units)激活函数的简称,它对输入按幅度加权,

使其成为非线性。

● hidden_dropout_prob:应用于全连接层的 dropout 概率。嵌入、编码器和汇聚

器层中都有全连接。输出不总是对序列内容的良好反映。汇聚隐藏状态的序

第3章 微调BERT 模型

列可改善输出序列。这里设置为0.1。

● hidden_size:编码器层的维度,也是汇聚层的维度,这里设置为768。

● initializer_range:初始化权重矩阵时的标准偏差值,这里设置为0.02。

· intermediate_size:编码器前馈层的维度,这里设置为3072。

● layer_norm_eps:是层规范化层的 epsilon 值,这里设置为le-12。

● max_position_embeddings:模型使用的最大长度,这里设置为512。

● model_type:模型的名称,这里设置为 bert。

● numattention_heads:注意力头数,这里设置为12。

· num_hidden_layers:层数,这里设置为12。

● pad_tokenid:使用0作为填充词元的HD,以避免对填充词元进行训练。

57

 · type_vocab_size:token_type_ids的大小用于标识序列。例如,“the dog[SEP]

 The cat.[SEP]”可用词元 ID [0,0,0,1,1,1]表示。

· vocab_size:模型用于表示 input_ids 的不同词元数量。换句话说,这是模型

可以识别和处理的不同词元或单词的总数。在训练过程中,模型会根据给定

的词表将文本输入转换为对应的词元序列,其中包含的词元数量是

vocab_size。通过使用这个词表,模型能够理解和表示更广泛的语言特征。这

里设置为 30522。

讲解完这些参数后,接下来将加载预训练模型。

"""

3.加载模型

现在开始加载预训练BERT模型

BertForSequenceClassification.from_pretrained 能够让你加载预训练的 BERT 模型权重,并且可以根据需求调整模型以适应特定的序列分类任务。这个方法非常实用,因为借助预训练的权重,模型通常能更快收敛,并且在特定任务上表现更优。第一个参数bert-base-uncased意思是加载BERT的默认权重,如果你有别的模型权重可以填写它的名字或者路径;nums_labels:表示你这个任务中的类别数,我们这个任务的label只有两种,所以这里是2。

DataParallel:DataParallel 是一种数据并行的实现方式,其核心思想是将大规模的数据集分割成若干个较小的数据子集,然后将这些子集分配到不同的计算节点(如 GPU)上,每个节点运行相同的模型副本,但处理不同的数据子集。在每一轮训练结束后,各节点会将计算得到的梯度进行汇总,并更新模型参数。如果不知道分布式计算的可以去看看我的另外一篇博客如何在多个GPU上训练

model=BertForSequenceClassification.from_pretrained("bert-base-uncased",num_labels=2)
model=nn.DataParallel(model)
model.to(device)

4.优化器分组参数

在将为模型的参数初始化优化器。在进行模型微调的过程中,首先需要初始化 预训练模型已学到的参数值。 微调一个预训练模型时,通常会使用之前在大规模数据上训练好的模型作为初始 模型。这些预训练模型已通过大量数据和计算资源进行了训练,学到了很多有用的特 征表示和参数权重。因此,我们希望在微调过程中保留这些已经学到的参数值,而不 是重新随机初始化它们。 所以,程序会使用预训练模型的参数值来初始化优化器,以便在微调过程中更好 地利用这些已经学到的参数。这样可以加快模型收敛速度并提高微调效果;

代码解释:

这段代码是用与为BERT模型的参数设置差异化的权重衰减策略,是训练Transformer模型时的常用技巧

param_optimizer是以字典的方式保存模型中所有可训练参数的名称和值,named_parameters()函数是获取模型中所有的参数名称和值。

no_decay是定义无需权重衰减的参数类型,权重衰减对偏置项bias和归一化层的weight无益,bias可能破坏模型对称性,LayerNorm的weight需保持灵活性,正则化会抑制其适应性。

optimizer_grouped_parametes: 组1:分组设置优化策略,筛选出参数名称中不包含bias和LayerNorm.weight的参数然后将权重衰减率设为0.1。组2:禁止权重衰减的参数,筛选出参数名称中包含bias和LayerNorm.weight的参数将权重衰减率设为0.0

param_optimizer=list(model.named_parameters())no_decay=['bias','LayerNorm.weight']
optimizer_grouped_parametes=[{'params':[p for n,p in param_optimizer if not any(nd in n for nd in no_decay)],'weight_decay_rate':0.1},{"params":[p for n,p in param_optimizer if any(nd in n for nd in no_decay)],'weight_decay_rate':0.0}
]

5.训练循环的超参数

训练循环中的超参数非常重要,尽管它们看起来可能无害。例如,Adam 优化器 会激活权重衰减并经历一个预热阶段。学习率(lr)和预热率(warnup)应该在优化阶段的早期设置为一个非常小的值,在一 定迭代次数后逐渐增加。这样可以避免出现过大的梯度和超调问题,以更好地优化模 型目标。

代码解释:

使用AdamW优化器,将模型的参数传入,并设置初始学习率为2e-5

定义一个函数calculate_accuracy来度量准确率,用于测试结果与标注进行比较,向函数中传入预测结果的概率分布和真实标签,pred_flat是查找这个概率分布中的最大概率的索引,flatten是将数组一维化,labels_flat是真实结果的一维化。最后,计算预测结果展平后的数组中与展平后的标签数组相等的元素数量占标签数组长度的比例,并将这个比例作为结果返回。

optimizer=AdamW(optimizer_grouped_parametes,lr=2e-5)
def calculate_accuracy(preds, labels):"""计算准确率的优化版本"""preds = np.argmax(preds, axis=1).flatten()labels = labels.flatten()return np.sum(preds == labels) / len(labels)

五、训练循环

我们的训练循环将遵循标准的学习过程。轮数(epochs)设置为 4,并将绘制损失和 准确率的度量值。训练循环使用 dataloader 来加载和训练批量。我们将对训练过程进 行度量和评估。 首先初始化 train_loss_set(用于存储损失和准确率的数值,以便后续绘图)。然后 开始训练每一轮,并运行标准的训练循环,

代码解释:

这段代码实现了BERT模型的完整训练和验证流程,包含以下核心步骤:

  1. 初始化训练记录容器
  2. 循环训练多个epoch
  3. 每个epoch包含训练阶段和验证阶段
  4. 记录并输出训练指标

train_loss_history:  储存每个epoch的平均训练损失

val_accuracy_history:存储每个epoch的验证集准确率

代码太多了,大家在代码中看注释吧,这里主要说一下训练步骤

1.初始化记录容器

2.设置外层epoch(循环次数)循环

3.设置model为训练模式

4.将数据挨个前向传播和反向传播更新参数

5.计算平均训练损失

6.将model设置为评估阶段并进行评估

7.计算平均验证准确率

8.打印训练信息

train_loss_history = []  # 存储每个epoch的平均训练损失
val_accuracy_history = []  # 存储每个epoch的验证集准确率for epoch_i in trange(epochs, desc="Epoch"):# ========== 训练阶段 ==========model.train()  # 设置模型为训练模式total_train_loss = 0  # 初始化累计损失for batch in train_dataloader:# 数据转移到GPUb_input_ids, b_input_mask, b_labels = tuple(t.to(device) for t in batch)# 梯度清零model.zero_grad()# 前向传播outputs = model(b_input_ids,attention_mask=b_input_mask,labels=b_labels)# 多GPU处理:取平均损失loss = outputs.loss.mean()# 反向传播loss.backward()"""在深度学习训练时,梯度可能会变得非常大,这会导致训练不稳定,甚至引发梯度爆炸的问题。torch.nn.utils.clip_grad_norm_ 函数通过对梯度的范数进行裁剪,避免梯度变得过大,从而让训练过程更加稳定。"""torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)  # 梯度裁剪# 参数更新optimizer.step()scheduler.step()total_train_loss += loss.item()# 记录平均训练损失avg_train_loss = total_train_loss / len(train_dataloader)train_loss_history.append(avg_train_loss)  # 记录历史损失# ========== 验证阶段 ==========model.eval()total_eval_accuracy = 0model.eval()  # 设置模型为评估模式total_eval_accuracy = 0  # 初始化累计准确率with torch.no_grad():  # 禁用梯度计算for batch in val_dataloader:b_input_ids, b_input_mask, b_labels = tuple(t.to(device) for t in         batch)# 前向传播outputs = model(b_input_ids, attention_mask=b_input_mask)# .logits是将model中还没有经过激活函数的值提取出来,因为验证不需要激活# 这样节省了显存,.detach将张量从计算图中分离,​断开梯度追踪。因为验证阶段不需要计算梯度,这一步可以节省内存并避免不必要的计算。# .cpu():如果张量在GPU上(例如通过.to('cuda')加载),这一步会将其移动到CPU内存中。NumPy无法直接处理GPU上的张量,必须转移到CPU。logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.to('cpu').numpy()total_eval_accuracy += calculate_accuracy(logits, label_ids)avg_val_accuracy = total_eval_accuracy / len(val_dataloader)val_accuracy_history.append(avg_val_accuracy)# 打印训练信息print(f"\nEpoch {epoch_i + 1}/{epochs}")print(f"Train loss: {avg_train_loss:.4f}")print(f"Validation Accuracy: {avg_val_accuracy:.4f}")

训练图解:

graph TDA[开始训练] --> B[设置训练模式]B --> C[遍历训练数据]C --> D[数据转GPU]D --> E[梯度清零]E --> F[前向传播]F --> G[计算损失]G --> H[反向传播]H --> I[梯度裁剪]I --> J[参数更新]J --> K[学习率更新]K --> CC --> L[计算平均损失]L --> M[验证模式]M --> N[遍历验证数据]N --> O[前向传播]O --> P[计算准确率]P --> Q[计算平均准确率]Q --> R[记录结果]R --> S[打印信息]S --> T[完成epoch?]T --是--> U[结束训练]T --否--> B

 六、使用测试数据集进行预测和评估

们使用了in_domain_traintsv 数据集训练 BERT下游模型。现在我们将使用基 于留出法!分出的测试数据集 outof_domain_dev.v 文件进行预测。我们的目标是预 测句子在语法上是否正确。 以下代码展示了测试数据准备过程:

# 加载测试数据
test_df = pd.read_csv("out_of_domain_dev.tsv", delimiter='\t', header=None,names=['sentence_source', 'label', 'label_notes', 'sentence'])# 预处理测试数据
test_input_ids, test_attention_masks, test_labels = preprocess_data(test_df, tokenizer, max_len=128)# 创建预测DataLoader
prediction_dataset = TensorDataset(test_input_ids, test_attention_masks, test_labels)
prediction_dataloader = DataLoader(prediction_dataset, sampler=SequentialSampler(prediction_dataset),batch_size=batch_size)# 初始化存储
predictions = []
true_labels = []model.eval()
for batch in prediction_dataloader:batch = tuple(t.to(device) for t in batch)b_input_ids, b_input_mask, b_labels = batchwith torch.no_grad():outputs = model(b_input_ids, attention_mask=b_input_mask)logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.cpu().numpy()predictions.append(logits)true_labels.append(label_ids)# 计算准确率
flat_predictions = np.concatenate(predictions, axis=0)
flat_predictions = np.argmax(flat_predictions, axis=1)
flat_true_labels = np.concatenate(true_labels, axis=0)accuracy = np.sum(flat_predictions == flat_true_labels) / len(flat_true_labels)
print(f"Test Accuracy: {accuracy:.4f}")

七、使用马修斯相关系数(MCC)评估

​1、MCC的核心原理与优势

马修斯相关系数(Matthews Correlation Coefficient, MCC)是一种综合评估二分类模型性能的指标,尤其适用于类别不平衡数据集。其优势包括:

  1. 全面性:同时考虑真阳性(TP)、真阴性(TN)、假阳性(FP)、假阴性(FN)。、
  2. 鲁棒性:在类别分布不均衡时仍能准确反映模型性能(例如医学诊断中的罕见病检测)。
  3. 可解释性:取值范围为[-1, 1],1表示完美预测,0表示随机猜测,-1表示完全错误。

计算公式:

2. 代码实现:

1. 测试数据预处理与预测
# 加载测试数据(示例路径需替换为实际路径)
test_df = pd.read_csv("out_of_domain_dev.tsv", delimiter='\t', header=None,names=['sentence_source', 'label', 'label_notes', 'sentence'])# 预处理(复用preprocess_data函数)
test_input_ids, test_attention_masks, test_labels = preprocess_data(test_df, tokenizer, max_len=128)# 创建DataLoader
prediction_dataset = TensorDataset(test_input_ids, test_attention_masks, test_labels)
prediction_dataloader = DataLoader(prediction_dataset, sampler=SequentialSampler(prediction_dataset), batch_size=batch_size)
2.模型预测与结果收集

 

# 初始化存储
predictions = []
true_labels = []model.eval()
for batch in prediction_dataloader:batch = tuple(t.to(device) for t in batch)b_input_ids, b_input_mask, b_labels = batchwith torch.no_grad():outputs = model(b_input_ids, attention_mask=b_input_mask)logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.cpu().numpy()predictions.append(logits)true_labels.append(label_ids)# 合并结果
flat_predictions = np.concatenate(predictions, axis=0)
flat_predictions = np.argmax(flat_predictions, axis=1)  # 将logits转为类别(0/1)
flat_true_labels = np.concatenate(true_labels, axis=0)
3.计算MCC
from sklearn.metrics import matthews_corrcoefmcc = matthews_corrcoef(flat_true_labels, flat_predictions)
print(f"Test MCC: {mcc:.4f}")

到这里就完美收官咯!!!!! 大家点个赞吧!!!

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

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

相关文章

10组时尚复古美学自然冷色调肖像电影照片调色Lightroom预设 De La Mer – Nautical Lightroom Presets

De La Mer 预设系列包含 10 种真实的调色预设,适用于肖像、时尚和美术。为您的肖像摄影带来电影美学和个性! De La Mer 预设非常适合专业人士和业余爱好者,可在桌面或移动设备上使用,为您的摄影项目提供轻松的工作流程。这套包括…

SDL多窗口多线程渲染技术解析

SDL多窗口多线程渲染技术解析 技术原理 SDL多线程模型与窗口管理 SDL通过SDL_Thread结构体实现跨平台线程管理。在多窗口场景中,每个窗口需关联独立的渲染器,且建议遵循以下原则: 窗口与渲染器绑定:每个窗口创建时生成专属渲染器(SDL_CreateRenderer),避免跨线程操作…

QT 跨平台发布指南

一、Windows 平台发布 1. 使用 windeployqt 工具 windeployqt --release --no-compiler-runtime your_app.exe 2. 需要包含的文件 应用程序 .exe 文件 Qt5Core.dll, Qt5Gui.dll, Qt5Widgets.dll 等 Qt 库 platforms/qwindows.dll 插件 styles/qwindowsvistastyle.dll (如果使…

L2-037 包装机 (分数25)(详解)

题目链接——L2-037 包装机 问题分析 这个题目就是模拟了物品在传送带和筐之间的传送过程。传送带用队列模拟,筐用栈模拟。 输入 3 4 4 GPLT PATA OMSA 3 2 3 0 1 2 0 2 2 0 -1输出 根据上述操作,输出的物品顺序是: MATA样例分析 初始…

机器学习的一百个概念(4)下采样

前言 本文隶属于专栏《机器学习的一百个概念》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…

qt6下配置qopengl

qt部件选择 Qt 6:需要手动选择 Qt Shader Tools 和 Qt 5 Compatibility Module(如果需要兼容旧代码) cmake文件 cmake_minimum_required(VERSION 3.16) # Qt6 推荐最低 CMake 3.16 project(myself VERSION 0.1 LANGUAGES CXX)set(CMAKE_A…

数据安全系列4:密码技术的应用-接口调用的身份识别

传送门 数据安全系列1:开篇 数据安全系列2:单向散列函数概念 数据安全系列3:密码技术概述 什么是认证? 一谈到认证,多数人的反应可能就是"用户认证" 。就是应用系统如何识别用户的身份,直接…

STL之map和set

1. 关联式容器 vector、list、deque、 forward_list(C11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。 关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结…

Vue3 其它API Teleport 传送门

Vue3 其它API Teleport 传送门 在定义一个模态框时,父组件的filter属性会影响子组件的position属性,导致模态框定位错误使用Teleport解决这个问题把模态框代码传送到body标签下

C++练习

1.将File练习题&#xff0c;内部的FILE*描述符&#xff0c;改成int描述符 2。写一个类Fifo管道类。提高难度&#xff0c;什么都不提示。只要求&#xff1a;使用自己编写的Fifo类对象&#xff0c;实现2个终端之间互相聊天 file.cpp #include <iostream> #include <c…

《Python Web网站部署应知应会》No4:基于Flask的调用AI大模型的高性能博客网站的设计思路和实战(上)

基于Flask的调用AI大模型的高性能博客网站的设计思路和实战&#xff08;上&#xff09; 摘要 本文详细探讨了一个基于Flask框架的高性能博客系统的设计与实现&#xff0c;该系统集成了本地AI大模型生成内容的功能。我们重点关注如何在高并发、高负载状态下保持系统的高性能和…

实现一个简易版的前端监控 SDK

【简易版的前端监控系统】 1、Promise的错误如何监控&#xff1f;–promise不是所有都是接口请求 2、接口的报错如何监控&#xff1f;–全局监控sdk&#xff0c;不改动公共的请求方法、不改动业务代码&#xff1b;一般接口使用axios请求 3、资源的报错如何监控&#xff1f; 4、…

【操作系统】软中断vs硬中断

在操作系统中&#xff0c;中断&#xff08;Interrupt&#xff09; 是 CPU 响应外部事件的重要机制&#xff0c;分为 硬中断&#xff08;Hardware Interrupt&#xff09; 和 软中断&#xff08;Software Interrupt&#xff09;。它们的核心区别在于 触发方式 和 处理机制。 1. 硬…

力扣刷题-热题100题-第27题(c++、python)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/merge-two-sorted-lists/description/?envTypestudy-plan-v2&envIdtop-100-liked 常规法 创建一个新链表&#xff0c;遍历list1与list2&#xff0c;将新链表指向list1与list2…

Python包下载路径 Chrome用户数据 修改到非C盘

查看 site-packages 是否能通过命令行完成&#xff1f; 可以&#xff0c;使用以下命令&#xff08;不需写脚本&#xff09;&#xff1a; python -m site输出包含&#xff1a; sys.path site-packages 路径&#xff08;全局和用户级&#xff09; 如果只想看安装路径&#…

【鸿蒙5.0】鸿蒙登录界面 web嵌入(隐私页面加载)

在鸿蒙应用中嵌入 Web 页面并加载隐私页面&#xff0c;可借助 WebView 组件来实现。以下是一个完整示例&#xff0c;展示如何在鸿蒙 ArkTS 里嵌入 Web 页面并加载隐私政策页面。 在 HarmonyOS 应用开发中&#xff0c;如果你希望嵌入一个网页&#xff0c;并且特别关注隐私页面加…

AI加Python的文本数据情感分析流程效果展示与代码实现

本文所使用数据来自于梯田景区评价数据。 一、数据预处理 数据清洗 去除重复值、空值及无关字符(如表情符号、特殊符号等)。 提取中文文本,过滤非中文字符。 统一文本格式(如全角转半角、繁体转简体)。 中文分词与去停用词 使用 jieba 分词工具进行分词。 加载自定义词…

Microi吾码界面设计引擎之基础组件用法大全【内置组件篇·上】

&#x1f380;&#x1f380;&#x1f380; microi-pageengine 界面引擎系列 &#x1f380;&#x1f380;&#x1f380; 一、Microi吾码&#xff1a;一款高效、灵活的低代码开发开源框架【低代码框架】 二、Vue3项目快速集成界面引擎 三、Vue3 界面设计插件 microi-pageengine …

【多线程】单例模式和阻塞队列

目录 一.单例模式 1. 饿汉模式 2. 懒汉模式 二.阻塞队列 1. 阻塞队列的概念 2. BlockingQueue接口 3.生产者-消费者模型 4.模拟生产者-消费者模型 一.单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种常用的软件设计模式&#xff0c;其核心思想是确保…

终值定理的推导与理解

终值定理的推导与理解 终值定理是控制理论和信号处理中的一个重要工具&#xff0c;它通过频域的拉普拉斯变换来分析时间域函数的最终稳态值。具体来说&#xff0c;终值定理提供了一个简便的方法&#xff0c;利用 F ( s ) F(s) F(s)&#xff08; f ( t ) f(t) f(t) 的拉普拉斯…