自然语言处理:基于BERT预训练模型的微博文本分类(使用PyTorch)

一、引言

  在当今数字社交时代,微博已经成为人们分享生活、观点和信息的主要平台之一。然而,伴随着微博的广泛使用,垃圾信息、广告以及无关紧要的内容也开始泛滥。当我们想要在庞大而多样的微博签到文本中寻找有用信息时,面临的一个挑战是识别和过滤掉其中的垃圾文本,例如广告、噪音和无关紧要的内容。这个任务是如何处理和过滤这些文本,以提供更有价值的信息。
  微博分类任务是一个有趣且具有挑战性的问题,它要求我们应用最新的自然语言处理技术,如BERT,来自动识别和分类微博签到文本。在这个任务中,我们的目标是建立微博文本分类模型,能够自动将微博文本分为两个主要类别:垃圾文本和非垃圾文本。通过实现这一目标,我们可以提供更清洁和有用的微博内容。
  本任务我们将深入探讨微博分类任务的各个方面,包括数据预处理、模型训练和性能评估等关键步骤。我们将展示如何利用BERT这一先进的自然语言处理模型,结合监督学习技术,来解决这一实际问题。
  BERT(Pre-training of Deep Bidirectional Transformers for
Language Understanding)是一种预训练的深度学习模型,能够理解自然语言的上下文,因此在文本分类、问答、命名实体识别等各种NLP任务上表现出色。建议大家阅读原文了解更多细节。

标注数据、预测数据和bert-base-chinese模型可以在下方链接获取
链接https://pan.baidu.com/s/1c9o0AUJBrQaiM0MlOPOFOg?pwd=1111

二、模型训练

2.1 导入包

import os
os.environ['TRANSFORMERS_CACHE'] = 'My_Model'
os.environ['CUDA_LAUNCH_BLOCKING'] = "1"

import pandas as pd
import numpy as np

import torch
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
import transformers
from transformers import BertConfig, BertTokenizer, BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup
from tqdm import trange, notebook
from tqdm import *

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score,f1_score, roc_auc_score

import warnings
warnings.filterwarnings("ignore")
print(f'torch version: {torch.__version__}\ntransformers version: {transformers.__version__}')

实验环境的 torch 和 transformer 版本

torch version: 2.0.1+cu117
transformers version: 4.27.1

2.2 导入标注数据

# 目标:保留话题的文字、移除poi超链接、视频超链接、保留表情图片中的标题文字、移除其他html标签
def prepare(text):
    import re
    # 删除包含 wbicon 的 <i> 标签以及它们之间的内容
    tokens = re.sub(re.compile(r'(<i\s+.*?wbicon.*?>.*?</i>)', re.S), '', text)
    # 删除与 HTML 匹配的标签,包括尖括号 < 和 > 之间的内容
    tokens = re.sub(re.compile(r'<(.*?)>', re.S), '', text)
    # 保留 <img title="xxx"> 中的 title 信息
    tokens = re.sub(re.compile(r'<img.*?(alt=["|\']{0,1}(.*?)["|\']{0,1}|title=["|\']{0,1}(.*?)["|\']{0,1})\s+.*?>', re.S|re.M), '\g<2>', text).strip()
    # 将匹配到的 <br/> 替换为 \n
    tokens = re.sub(re.compile(r'<br/>',re.S),'\n',text)
    # 移除文本内的连续重复内容
    # 移除连续发生3次及以上次数的重复性内容
    # 重复内容的字符串长度>=3
    tokens = re.sub(re.compile(r'([\s|\S]{2,}?)\1{2,}',re.S|re.M),'\g<1>',text)
    # 移除 poi链接、视频链接、直播链接
    urls=re.findall(r"<a.*?href=.*?<\/a>", text, re.I|re.S|re.M)
    url=[u for u in urls if '>2<' in u or 'location_default.png' in u or '视频</a>' in u or '视频</span></a>' in u or '直播</a>' in u]
    if len(url)>0:
        for u in url:
            tokens=text.replace(u,'')
            
    return tokens
df_label = pd.read_csv('data/weibo_label.csv')

# pandarallel 可以加速 pandas 运算速度
import psutil
from pandarallel import pandarallel
pandarallel.initialize(nb_workers=psutil.cpu_count(logical=False))

# 数据处理,调用了上面定义的 prepare 函数
df_label['message'] = df_label['message'].parallel_apply(prepare)
df_label.sentiment.value_counts()  # 查看各标签文本数量
sentiment
 0    2771
 1    2102
-1     683
 6     638
Name: count, dtype: int64
  • 0表示中性文本
  • 1表示正面情感文本
  • -1表示负面情感文本
  • 6表示垃圾文本

再进行处理,重新定义标签,我们将6赋值为0表示垃圾文本;不为6的标签赋值为1表示非垃圾文本

df = df_label.copy()
df.loc[df[df.sentiment!=6].index,'sentiment'] = 1

df = df[(df['sentiment']==1)|(df['sentiment']==6)]
# 随机丢弃标注列表中量较多的数据,以保持二者的标注量基本相同,提高后期模型预测的准确率
drop_size = len(df[df['sentiment']==1].sentiment)-len(df[df['sentiment']==6].sentiment)
df.drop(df[df['sentiment']==1].sample(drop_size).index, inplace=True)

df.loc[df[df.sentiment==6].index,'sentiment'] = 0
df.sample(10)

在这里插入图片描述

2.3 模型初始化

model_name = 'bert-base-chinese'

# 下面三个文件的路径为 bert-base-chinese 文件夹,根据自己的存储路径更换
config = BertConfig.from_pretrained('../model/'+model_name, finetuning_task='binary')  # BERT 模型配置
tokenizer = BertTokenizer.from_pretrained('../model/'+model_name)  # BERT 的分词器
model = BertForSequenceClassification.from_pretrained('../model/'+model_name, num_labels=2)  # BERT 的文本分类模型
 
# 用于将文本转换为BERT模型的输入标记
def get_tokens(text, tokenizer, max_seq_length, add_special_tokens=True): 
    # 使用分词器将文本转换为模型可以接受的输入格式
    input_ids = tokenizer.encode(text,
                                 add_special_tokens=add_special_tokens,
                                 truncation=True,
                                 max_length=max_seq_length,
                                 pad_to_max_length=True)
    # 创建一个关注掩码,标记哪些标记是真实文本标记
    attention_mask = [int(id > 0) for id in input_ids]
    # 确保输入标记和关注掩码的长度等于最大序列长度
    assert len(input_ids) == max_seq_length
    assert len(attention_mask) == max_seq_length
    return (input_ids, attention_mask)

2.4 数据集划分

X_train, X_test, Y_train, Y_test = train_test_split(df['message'],  # 文本消息数据
                                                    df['sentiment'],  # 文本情感标签
                                                    test_size=0.2,    # 测试集占总数据的比例
                                                    random_state=42,  # 随机种子,以确保可重复性
                                                    stratify=df['sentiment'])  # 根据情感标签进行分层抽样
# 使用自定义函数 get_tokens 对训练集和测试集的文本进行分词,每个文本最多包含150个标记
X_train_tokens = X_train.apply(get_tokens, args=(tokenizer, 150))
X_test_tokens = X_test.apply(get_tokens, args=(tokenizer, 150))

2.5 训练准备

# 将训练集的文本特征转换为PyTorch张量
input_ids_train = torch.tensor(
    [features[0] for features in X_train_tokens.values], dtype=torch.long)  # 输入特征 ID
input_mask_train = torch.tensor(
    [features[1] for features in X_train_tokens.values], dtype=torch.long)  # 输入掩码
label_ids_train = torch.tensor(Y_train.values, dtype=torch.long)  # 标签 ID

# # 输出训练集张量的形状
# print(input_ids_train.shape)  # 输出训练集输入特征的形状
# print(input_mask_train.shape)  # 输出训练集输入掩码的形状
# print(label_ids_train.shape)  # 输出训练集标签的形状

# 创建训练数据集
train_dataset = TensorDataset(input_ids_train, input_mask_train, label_ids_train)

# 将测试集的文本特征转换为PyTorch张量
input_ids_test = torch.tensor([features[0] for features in X_test_tokens.values], dtype=torch.long)
input_mask_test = torch.tensor([features[1] for features in X_test_tokens.values], dtype=torch.long)
label_ids_test = torch.tensor(Y_test.values, dtype=torch.long)

# 创建测试数据集
test_dataset = TensorDataset(input_ids_test, input_mask_test, label_ids_test)
# 训练批次大小和训练周期数
train_batch_size = 64
num_train_epochs = 3

# 创建训练数据采样器和数据加载器
train_sampler = RandomSampler(train_dataset)  # 随机采样器,用于随机选择训练样本
train_dataloader = DataLoader(train_dataset, 
                              sampler=train_sampler, 
                              batch_size=train_batch_size)  # 创建训练数据加载器
t_total = len(train_dataloader) // num_train_epochs  # 计算总的训练步数

# 输出一些训练相关的信息
print("样本数量 =", len(train_dataset))  # 输出训练集样本数量
print("训练周期数 =", num_train_epochs)  # 输出训练周期数
print("总的训练批次大小 =", train_batch_size)  # 输出总的训练批次大小
print("总的优化步数 =", t_total)  # 输出总的优化步数

# 优化器和学习率调度器的设置
learning_rate = 5e-5  # 学习率
adam_epsilon = 1e-8  # Adam优化器的epsilon值
warmup_steps = 0  # 学习率预热步数

# 创建AdamW优化器和学习率调度器
optimizer = AdamW(model.parameters(), lr=learning_rate, eps=adam_epsilon)
scheduler = get_linear_schedule_with_warmup(optimizer, 
                                            num_warmup_steps=warmup_steps, 
                                            num_training_steps=t_total)  # 创建学习率调度器

在这里插入图片描述

2.6 训练

# 检测是否有GPU可用,如果有则使用GPU,否则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 创建一个描述训练周期的迭代器
train_iterator = trange(num_train_epochs, desc="Epoch")

# 将模型置于 train 模式
model.train()

for epoch in train_iterator:
    # 创建一个描述迭代的迭代器
    epoch_iterator = tqdm(train_dataloader, desc="Iteration")
    for step, batch in enumerate(epoch_iterator):
        # 重置每个迭代开始时的所有梯度
        model.zero_grad()
        
        # 将模型和输入数据移到GPU(如果可用)
        # torch.cuda.empty_cache()  # 清理GPU缓存
        model.to(device)  # 将模型移到GPU或CPU
        cuda = next(model.parameters()).device
        batch = tuple(t.to(cuda) for t in batch)  # 将批次数据移到GPU或CPU

        # 确定传递给模型的输入
        inputs = {
            'input_ids': batch[0],      # 输入特征ID
            'attention_mask': batch[1], # 输入掩码
            'labels': batch[2]         # 标签
        }

        # 通过模型进行前向传播:输入 -> 模型 -> 输出
        outputs = model(**inputs)

        # 计算损失
        loss = outputs[0]

        # 打印当前损失值
        print("\r%f" % loss, end='')

        # 反向传播损失,自动计算梯度
        loss.backward()

        # 通过将梯度限制在一定范围内来防止梯度爆炸
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)

        # 更新模型参数和学习率
        optimizer.step()
        scheduler.step()

在这里插入图片描述
保存模型

model.save_pretrained('My_Model/weibo-bert-rubbish-model')

2.7 验证

# 测试批次大小
test_batch_size = 64

# 创建测试数据采样器和数据加载器
test_sampler = SequentialSampler(test_dataset)  # 顺序采样器,用于顺序选择测试样本
test_dataloader = DataLoader(test_dataset, 
                             sampler=test_sampler, 
                             batch_size=test_batch_size)  # 创建测试数据加载器

# 加载之前保存的预训练模型
# model = model.from_pretrained('/outputs')

# 初始化预测和实际标签
preds = None
out_label_ids = None

# 将模型置于 eval 模式
model.eval()

for batch in tqdm(test_dataloader, desc="评估中"):
    # 将模型和输入数据移到GPU(如果可用)
    model.to(device)
    batch = tuple(t.to(device) for t in batch)
    
    # 在 eval 模式下不跟踪任何梯度
    with torch.no_grad():
        inputs = {
            'input_ids': batch[0],  # 输入特征ID
            'attention_mask': batch[1],  # 输入掩码
            'labels': batch[2]  # 标签
        }        

        # 通过模型进行前向传播
        outputs = model(**inputs)

        # 我们得到损失,因为我们提供了标签
        tmp_eval_loss, logits = outputs[:2]

        # 测试数据集可能包含多个批次的项目
        if preds is None:
            preds = logits.detach().cpu().numpy()
            out_label_ids = inputs['labels'].detach().cpu().numpy()
        else:
            preds = np.append(preds, logits.detach().cpu().numpy(), axis=0)
            out_label_ids = np.append(out_label_ids, 
                                      inputs['labels'].detach().cpu().numpy(), 
                                      axis=0)

# 计算最终损失、预测和准确度
preds = np.argmax(preds, axis=1)  # 获取预测类别
acc_score = accuracy_score(preds, out_label_ids)  # 计算准确度
f1_score = f1_score(preds, out_label_ids)  # 计算F1分数
print ('测试集中的Accuracy分数: ', acc_score)
print ('测试集中的F1分数: ', f1_score)

在这里插入图片描述

2.8 预测

读取待分类微博数据,使用微调模型进行类别预测

df_origin = pd.read_csv('data/weibo_origin.csv')
df_origin['label'] = 0 #统一初始化为0
df_origin['text'] = df_origin.message.str.replace('\n',' ')
df_origin

在这里插入图片描述

# 这里更上面创建训练和验证集一样的道理
X_pred=df_origin['text']
Y_pred=df_origin['label']
X_pred_tokens = X_pred.parallel_apply(get_tokens, args=(tokenizer, 150))

input_ids_pred = torch.tensor(
    [features[0] for features in X_pred_tokens.values], dtype=torch.long)
input_mask_pred = torch.tensor(
    [features[1] for features in X_pred_tokens.values], dtype=torch.long)
label_pred=torch.tensor(Y_pred.values,dtype=torch.long)
pred_dataset = TensorDataset(input_ids_pred,input_mask_pred,label_pred)

pred_batch_size = 256
pred_sampler = SequentialSampler(pred_dataset)
pred_dataloader = DataLoader(pred_dataset, 
                             sampler=pred_sampler, 
                             batch_size=pred_batch_size)

调用微调模型,也就是刚才训练好的模型

model = model.from_pretrained('My_Model/weibo-bert-rubbish-model')
preds = None
model.eval()

在这里插入图片描述

# 预测
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
for batch in tqdm(pred_dataloader, desc="Predict"):
    
    batch = tuple(t.to(device) for t in batch)
    
    with torch.no_grad():
        inputs = {
            'input_ids': batch[0],
            'attention_mask': batch[1],
            'labels': batch[2]
        }

        outputs = model(**inputs)
        _, logits = outputs[:2]

        if preds is None:
            preds = logits.detach().cpu().numpy()
        else:
            preds = np.append(preds, logits.detach().cpu().numpy(), axis=0)

得到预测结果

prob = torch.nn.functional.softmax(torch.tensor(preds), dim=1)  # 使用softmax函数计算预测的概率分布
preds = np.argmax(preds, axis=1)  # 计算每个样本的最终预测类别
df_origin['ad_prob'] = [p[1].item() for p in prob]  # 将概率分布的第二列(表示"1"类别的概率)添加到DataFrame中
df_origin['pred'] = preds  # 将最终的预测类别添加到DataFrame中
df_origin.to_csv('data/weibo_pre.csv', index=False)
df_origin.sample(10)

在这里插入图片描述

三、总结

  1. F1分数和精确度:模型在评估阶段获得了非常良好的性能,F1分数和精确度都超过了80%。这表明模型在测试数据上具有很高的分类性能,可以有效地识别文本数据中的目标情感或类别。

  2. 训练过程:在训练期间,损失(loss)在下降。这是一个积极的迹象,表明模型正在逐渐学习并适应训练数据。随着训练的进行,模型逐渐提高其性能,以使损失最小化。

  3. 训练设备:我自己的电脑显卡为1660S,6G的显存,训练的时候报错,建议大家使用本文章代码时使用显存大一点的电脑。
    在这里插入图片描述

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用BERT预训练模型进行中文文本分类是一种常见的应用。下面我将以基于PyTorch的方式进行介绍。 步骤一:准备数据 首先,我们需要准备用于训练和测试的文本数据集。可以选择一个适合任务的中文文本分类数据集,例如THUCNews等。将数据集划分为训练集和测试集。 步骤二:安装和导入依赖 在使用PyTorch进行BERT模型的文本分类之前,需要安装相关的软件包。首先安装transformers库,该库提供了许多预训练的BERT模型。另外,还需要安装torch和tqdm库用于训练和进度条显示。 步骤三:加载预训练模型 使用transformers库加载预训练的中文BERT模型,例如'bert-base-chinese'。可以通过以下代码进行加载: ``` from transformers import BertTokenizer, BertForSequenceClassification # 加载预训练模型 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=num_labels) ``` 这里需要根据具体的文本分类任务设置num_labels参数,表示分类的类别数。 步骤四:预处理数据 对训练集和测试集的文本进行预处理。这包括将文本转换为BERT模型所需的输入格式,即将文本转化为token ids,并将文本序列padding到相同的长度。 步骤五:定义训练和评估循环 定义训练和评估模型的循环。在每个训练batch中,将输入传递给BERT模型,获取模型的预测结果。然后计算损失并进行反向传播优化模型参数。 步骤六:训练模型 使用准备好的训练集对模型进行训练。根据任务需要选择合适的优化器和学习率,设置训练的epochs和batch size等参数。在训练过程中,可以通过打印损失和精度等指标来监控训练进展。 步骤七:评估模型 使用准备好的测试集对训练好的模型进行评估。计算模型在测试集上的准确率、精确率、召回率等指标,以评估模型的性能。 步骤八:保存和加载模型 可以选择保存训练好的模型以备后续使用。可以使用torch.save方法保存模型,并使用torch.load方法加载模型。 使用BERT预训练模型进行中文文本分类可以较好地捕捉词义和语义的特征,提升分类任务的性能。通过以上步骤,可以实现一个简单的中文文本分类模型。当然,根据实际需求,还可以进行模型调优、模型融合等进一步的优化操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值