当前位置:   article > 正文

使用BERT模型实现文本二分类任务(包括数据不平衡问题)_bert文本二分类

bert文本二分类

我的分类数据储存在两个excel表格中,一部分是已经编码好的,用以训练模型;一部分是未编码的,需要训练好的模型对其预测。需要注意的是,我的任务本来是多分类任务,但是由于数据本身存在不平衡的现象,计算机辅助分类的效果很差,因此我将多分类任务转化成多个二分类任务,也就是说,这段python脚本只需要判断输入的文本是否属于某个类别即可。这种做法可以在一定程度上提高模型性能。

import pandas as pd
import numpy as np
from sklearn.metrics import precision_recall_fscore_support, accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
import torch
from torch.utils.data import Dataset
import torch.nn.functional as F

这是进行分类任务时使用到的一些python包,它们的作用如下:

(1)import pandas as pd: 导入Pandas库,用于数据处理。

(2)import numpy as np: 导入NumPy库,用于数值计算。

(3)from sklearn.metrics import precision_recall_fscore_support, accuracy_score: 从scikit-learn库中导入评估指标函数。

(4)from sklearn.model_selection import train_test_split: 从scikit-learn库中导入数据集划分函数。

(5)from sklearn.preprocessing import LabelEncoder: 从scikit-learn库中导入标签编码器。

(6)from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments: 从transformers库中导入BERT相关的tokenizer、模型和训练器。

(7)import torch: 导入PyTorch库。

(8)from torch.utils.data import Dataset: 从PyTorch中导入数据集类。

(9)import torch.nn.functional as F: 从PyTorch中导入神经网络功能模块。

# 加载数据
df = pd.read_excel('已编码文件.xlsx')
df_unencoded = pd.read_excel('未编码文件.xlsx')

 因为我的原始数据是excel表格,所以需要转换成机器可以读取的形式

# 数据预处理
def preprocess_data(df):
    texts = df['内容'].tolist()
    labels = df['标签名'].tolist()
    return texts, labels

texts, labels = preprocess_data(df)

 这里是定义了一个函数将文本列和标签列分别转换成列表方便下一步的处理

# 确保所有文本都是字符串类型
texts = [str(text) for text in texts]

这一步主要是确保所有文本都是字符串格式,因为有时候获得的文本数据可能来源于微博这类社交平台,存在无法处理的文本,比如表情包。

# 标签编码
label_encoder = LabelEncoder()
labels_encoded = label_encoder.fit_transform(labels)

这一步将标签转换成数值编码

# 确认标签编码后的唯一值
print(f"Unique labels after encoding: {np.unique(labels_encoded)}")  # 检查是否有两个独特标签

因为是二分类任务,所以要检查是否只有两个标签,如果出现了其他值,在后面会出现错误。

到此为止全都是数据准备阶段,如果你输入的数据是储存在excel表格中,只需要将上述代码中那些中文名字更改成与你数据中相匹配的名字就可以了。

# 划分训练集和测试集
train_texts, test_texts, train_labels, test_labels = train_test_split(texts, labels_encoded, test_size=0.4, random_state=42)

这里我将数据集按照3:2的比例分成了两部分,一部分用于训练模型,一部分用以测试模型性能。当然比例还可以调整,一般训练集大于测试集就可以了,比如有的是按照4:1的比例分割。更改的话只需要将代码中test_size更改为你想要的数值,它代表测试集的大小。

# 使用BERT tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')

这一步是用来加载BERT tokenizer,它能将文本转换成适合BERT模型处理的格式。我在这使用的是谷歌给出的基于中文预训练的bert模型,如果你觉得这个模型效果不太好的话,可以使用哈工大或者百度训练的roberta模型,哈工大团队声称百度训练的模型更适合给社交媒体中的文本进行分类,但是直接加到我的代码中可能会导致假阳性样本过高,所以我没有使用。这些模型可以在Huggingface上找到(但请注意不是那个表情包网站)。

# 数据编码
train_encodings = tokenizer(train_texts, truncation=True, padding=True, max_length=512)
test_encodings = tokenizer(test_texts, truncation=True, padding=True, max_length=512)

# 创建PyTorch数据集
class TextDataset(Dataset):
    def __init__(self, encodings, labels):
        self.encodings = encodings
        self.labels = labels

    def __getitem__(self, idx):
        item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
        item['labels'] = torch.tensor(self.labels[idx]).long()
        return item

    def __len__(self):
        return len(self.labels)

train_dataset = TextDataset(train_encodings, train_labels)
test_dataset = TextDataset(test_encodings, test_labels)

这些仍然是处理数据的过程,基本没有要修改的地方,直接照搬即可

# 定义Focal Loss
def py_sigmoid_focal_loss(pred, target, weight=None, gamma=2.0, alpha=0.9, reduction='mean', avg_factor=None):
    pred_sigmoid = pred.sigmoid()
    target = torch.nn.functional.one_hot(target, num_classes=pred.shape[1]).type_as(pred)  # 转换为one-hot编码
    pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)
    focal_weight = (alpha * target + (1 - alpha) * (1 - target)) * pt.pow(gamma)
    loss = F.binary_cross_entropy_with_logits(pred, target, reduction='none') * focal_weight
    return loss.mean() if reduction == 'mean' else loss.sum()

这段代码用来解决文本分类中数据不均衡的问题。我们在做文本分类时经常会遇到正样本相对较少的情况,这会导致模型很难被训练到符合预期。解决问题的方法通常是过采样或者欠采样,这种方法面临比较复杂的分类任务效果相对较差。这时候我们使用Focal Loss函数来解决这个问题,它一方面直接在损失函数级别进行调整,无需修改数据集,另一方面,Focal Loss函数更加关注难分类的样本,有助于提升模型在少数类上的表现。经过试验发现效果的确有所提升。对于这段代码需要注意两个值,gamma和alpha,我们需要根据自身的数据集情况对这两个参数进行更改,我只更改了alpha。一般alpha的值为0.25,我在搜索资料之后发现,其实可以尝试设置为0.5,然后根据数据集的情况进行微调。我的数据集中正负样本的比例为1:9,所以我将alpha值设置的比较高。

# 自定义Trainer以使用Focal Loss
class FocalLossTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.get("labels")
        outputs = model(**inputs)
        logits = outputs.get("logits")
        loss = py_sigmoid_focal_loss(logits, labels)
        return (loss, outputs) if return_outputs else loss

这里默认损失函数使用Focal Loss函数

# 定义评估函数
def compute_metrics(p):
    preds = np.argmax(p.predictions, axis=1)
    precision, recall, f1, _ = precision_recall_fscore_support(p.label_ids, preds, average='binary')
    accuracy = accuracy_score(p.label_ids, preds)
    return {
        'accuracy': accuracy,
        'f1': f1,
        'precision': precision,
        'recall': recall
    }

定义函数来测算f1、accuracy、precision以及recall值,其中f1值是评价模型效果最重要的指标。当然,precision和recall要尽可能均衡。accuracy值重要程度相对较低。

# 定义BERT模型
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)

这里引入了BERT模型

# 定义训练参数
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=5,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir='./logs',
    logging_steps=10,
    evaluation_strategy="epoch",
    save_strategy="epoch",  # 设置保存策略为按epoch
    save_total_limit=2,
    load_best_model_at_end=True,
    report_to='none',  # 禁用报告
)

这里是一些参数,我的建议是epoch不要设置太高,一是效果不会提升太多,二是运行时间太长。

# 确保模型和数据在GPU上运行
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

因为BERT模型运行速度相对较慢,这里启用了GPU加快运行速度

# 创建Trainer
trainer = FocalLossTrainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=test_dataset,
    compute_metrics=compute_metrics
)

# 训练模型
trainer.train()

# 评估模型
trainer.evaluate()

接着是训练模型和评估模型,基本没有需要修改的地方。

# 加载未编码的数据并进行分类
unencoded_texts = df_unencoded.iloc[:, 0].tolist()
unencoded_encodings = tokenizer(unencoded_texts, truncation=True, padding=True, max_length=512)
unencoded_dataset = TextDataset(unencoded_encodings, np.zeros(len(unencoded_texts)))

# 使用模型进行预测
predictions = trainer.predict(unencoded_dataset)
unencoded_preds = np.argmax(predictions.predictions, axis=1)

最后是对未编码数据的预测,会返回一个excel表格。

PS:使用bert模型运行时间相对较长,基本每次分类任务都在一个半小时以上,但是效果还算可以。不过,如果要训练的数据的类别严重不平衡的话,还是不建议使用计算机辅助分类,这代表分类的依据本身可能存在一定的问题。此外,我还尝试了使用TEXTCNN进行分类,虽然运行速度很快,但是效果奇差无比。如果特别想要使用哈工大或者百度训练的roberta模型的话,需要先在Huggingface上将模型下载下来,保存在一个文件夹中,直接通过本地文件调用访问,不然因为“网络”问题无法访问,即使开了那个也不行。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号