基于Bert-base-chinese训练多分类文本模型(代码详解)

news2024/9/21 12:41:50

目录

一、简介

二、模型训练

三、模型推理


一、简介

BERT(Bidirectional Encoder Representations from Transformers)是基于深度学习在自然语言处理(NLP)领域近几年出现的、影响深远的创新模型之一。在BERT之前,已经有许多预训练语言模型,如ELMO和GPT,它们展示了预训练模型在NLP任务中的强大性能。然而,这些模型通常基于单向的上下文信息,即只考虑文本中的前向或后向信息,这限制了它们对文本的全局理解BERT旨在通过引入双向上下文信息来解决这一问题,从而更准确地表示文本中的语义信息

与传统的单向语言模型相比,BERT 的核心优势在于:

  • 双向性:BERT通过使用Transformer的编码器结构,能够同时从文本的左右两个方向学习上下文信息,使模型能够更好地理解句子中的每个词的语义。
  • 预训练与微调:通过预训练任务,BERT 可以在多种下游任务上进行快速微调。

其中,Bert-base-chinese模型是一个在简体和繁体中文文本上训练得到的预训练模型。

二、模型训练

数据示例如下,现在有一个data.csv文件,包含两列分别是特征(feature)和标签(label)。其中,标签可能是多个分类。

第一步:读取数据并提取出特征和标签
data = pd.read_csv('./data/data.csv', encoding='utf-8')   # 如果表格数据是gbk格式,则修改encoding='gbk'
X = data['feature']   # 特征列
y = data['label'].values   # 标签列
第二步:对标签数据进行编码转换
label_encoder = LabelEncoder()  # 初始化
y_encoded = label_encoder.fit_transform(y)
joblib.dump(label_encoder, './data/encoder.joblib') # 保存 label_encoder 以便以后使用
print(f'分类数:{len(label_encoder.classes_)} \n')  # 标签的类别数量
第三步:划分训练数据集和测试数据集
X_train, X_val, y_train, y_val = train_test_split(X, y_encoded, test_size=0.1,random_state=42)  # test_size=0.1表示训练集和测试集划分比例是9:1 
# random_state=42表示固定随机种子为42,保证每一次分割数据集都是一样的结果
第四步:加载BERT分词器
local_model_path = './bert-base-chinese'   # 模型地址
tokenizer = BertTokenizer.from_pretrained(local_model_path)
tokenizer.save_pretrained(best_model_path)
第五步:将文本数据转换成BERT模型能够理解的格式
def preprocess_for_bert(data, labels):
    input_ids = []
    attention_masks = []

    for sent in data:  # 对每个句子(sent)进行编码处理
        encoded_sent = tokenizer.encode_plus(
            text=sent,  # 要处理的句子
            add_special_tokens=True,  # 添加特殊标记,如句子的起始标记和结束标记
            max_length=256,  # 句子的最大长度为256个标记,超出部分将被截断,不足部分将被填充
            padding='max_length',  # 将句子填充到固定长度(256),不足部分会用0补齐
            return_attention_mask=True,  # 返回注意力掩码,用于标记哪些位置是填充部分,哪些位置是实际的句子内容
            truncation=True  # 如果句子超过了最大长度,进行截断
        )

        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))

    # 转换为PyTorch张量(tensor),以便后续可以输入到模型中进行训练或推理
    input_ids = torch.tensor(input_ids)
    attention_masks = torch.tensor(attention_masks)
    labels = torch.tensor(labels)

    return input_ids, attention_masks, labels


train_inputs, train_masks, train_labels = preprocess_for_bert(X_train, y_train)
val_inputs, val_masks, val_labels = preprocess_for_bert(X_val, y_val)
第六步:创建训练集DataLoader和测试集DataLoader
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=8)

validation_data = TensorDataset(val_inputs, val_masks, val_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=8)

第七步:加载BERT模型
model = BertForSequenceClassification.from_pretrained(local_model_path, num_labels=len(label_encoder.classes_),ignore_mismatched_sizes=True)
model.cuda()  # 默认使用第一张显卡,如果没有显卡,则可以注释改行代码
第八步:设置优化器和调度器
EPOCHS = 5   # 训练次数,可以自定义修改
optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)   # 优化器
total_steps = len(train_dataloader) * EPOCHS   # 训练步数
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps)  # 调度器
第九步:设置精确度的计算方式
def flat_accuracy(preds, labels):
    pred_flat = np.argmax(preds, axis=1).flatten()
    labels_flat = labels.flatten()
    return np.sum(pred_flat == labels_flat) / len(labels_flat)  # 通过比较预测类别和实际标签的相同之处,计算出预测正确的比例
第十步:训练和评估
best_model_path = './model' # 最优模型训练结果的保存路径
best_val_accuracy = 0  # 初始化最优精确度
for epoch in range(EPOCHS):
    model.train()  # 第一步:将模型设置为训练模式
    total_train_loss = 0  # 初始化训练总损失为0

    for step, batch in enumerate(train_dataloader):  # 第二步:加载训练集DataLoader
        b_input_ids = batch[0].cuda()  # 如果没有显卡,则可以将.cuda给删除了
        b_input_mask = batch[1].cuda()  # 如果没有显卡,则可以将.cuda给删除了
        b_labels = batch[2].cuda().long()  # 如果没有显卡,则可以将.cuda给删除了

        model.zero_grad() # 清除模型的梯度

        outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)  # 第三步:将输入数据传递给模型,得到模型的输出
        loss = outputs.loss # 第四步:提取出损失值,用于后续的反向传播
        total_train_loss += loss.item()
        loss.backward() # 第五步:进行反向传播,计算梯度
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        scheduler.step()

    avg_train_loss = total_train_loss / len(train_dataloader)  # 第六步:更新学习率

    torch.cuda.empty_cache()  # 训练一轮就清空一次显卡缓存,如果没有显卡,则注释

    # 第七步:模型测试,计算准确度,处理逻辑和训练差不多

    model.eval()
    total_eval_accuracy = 0
    total_eval_loss = 0

    for batch in validation_dataloader:  # 加载测试集DataLoader
        b_input_ids = batch[0].cuda()
        b_input_mask = batch[1].cuda()
        b_labels = batch[2].cuda().long()

        with torch.no_grad():
            outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)

        loss = outputs.loss
        total_eval_loss += loss.item()
        logits = outputs.logits
        logits = logits.detach().cpu().numpy()
        label_ids = b_labels.to('cpu').numpy()
        total_eval_accuracy += flat_accuracy(logits, label_ids)

    avg_val_accuracy = total_eval_accuracy / len(validation_dataloader)
    avg_val_loss = total_eval_loss / len(validation_dataloader)

    torch.cuda.empty_cache() # 验证一轮就清空一次显卡缓存,如果没有显卡,则注释

    print(f'Training loss: {avg_train_loss}')
    print(f'Validation loss: {avg_val_loss}')
    print(f'Validation Accuracy: {avg_val_accuracy}')   # 主要看这个精度,一般准确率90%以上就可以投入实际生产环境中

    # 在验证集上计算准确率
    if avg_val_accuracy > best_val_accuracy:
        best_val_accuracy = avg_val_accuracy
        # 保存模型
        model.save_pretrained(best_model_path)   # 根据训练次数,保存最优的一个模型结果
完整代码如下:
import pandas as pd
import numpy as np
import joblib
import torch
import time

from transformers import BertTokenizer, BertForSequenceClassification
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler, TensorDataset, random_split
from torch.optim import AdamW
from transformers import get_linear_schedule_with_warmup

# 读取数据
data = pd.read_csv('./data/data.csv', encoding='utf-8')   # 如果表格数据是gbk,则修改encoding='gbk'

# 最优模型训练结果的保存路径
best_model_path = './model'

X = data['feature']   # 特征列
y = data['label'].values   # 标签列

# 对标签数据进行编码转换
print("1、开始编码转换啦~")
label_encoder = LabelEncoder()  # 初始化
#label_encoder = joblib.load('./data/encoder.joblib')   # 当你使用同样的data第二次运行脚本时,就可以直接加载上一次保存的编码结果,而不需要重复编码(除非两次加载的数据有变动)
y_encoded = label_encoder.fit_transform(y)
print(f'分类数:{len(label_encoder.classes_)} \n')  # 标签的类别数量

# 保存 label_encoder 以便以后使用
joblib.dump(label_encoder, './data/encoder.joblib')

# 分割数据集
X_train, X_val, y_train, y_val = train_test_split(X, y_encoded, test_size=0.1,random_state=42)  # 这里训练和测试数据集比例是9:1,test_size=0.2或者0.3  固定随机种子42,保证每一次分割数据集都是一样的

# 加载BERT分词器
local_model_path = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(local_model_path)
tokenizer.save_pretrained(best_model_path)

# BERT预处理 -- 将文本数据转换成BERT模型能够理解的格式
def preprocess_for_bert(data, labels):
    input_ids = []
    attention_masks = []

    for sent in data:  # 对每个句子(sent)进行编码处理
        encoded_sent = tokenizer.encode_plus(
            text=sent,  # 要处理的句子
            add_special_tokens=True,  # 添加特殊标记,如句子的起始标记和结束标记
            max_length=256,  # 句子的最大长度为256个标记,超出部分将被截断,不足部分将被填充
            padding='max_length',  # 将句子填充到固定长度(256),不足部分会用0补齐
            return_attention_mask=True,  # 返回注意力掩码,用于标记哪些位置是填充部分,哪些位置是实际的句子内容
            truncation=True  # 如果句子超过了最大长度,进行截断
        )

        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))

    # 转换为PyTorch张量(tensor),以便后续可以输入到模型中进行训练或推理
    input_ids = torch.tensor(input_ids)
    attention_masks = torch.tensor(attention_masks)
    labels = torch.tensor(labels)

    return input_ids, attention_masks, labels


# 预处理数据
print("2、开始预处理数据啦~")
train_inputs, train_masks, train_labels = preprocess_for_bert(X_train, y_train)
val_inputs, val_masks, val_labels = preprocess_for_bert(X_val, y_val)

# 创建DataLoader
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=8)

validation_data = TensorDataset(val_inputs, val_masks, val_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=8)

# 加载BERT模型
print("3、开始预加载模型啦~")
model = BertForSequenceClassification.from_pretrained(local_model_path, num_labels=len(label_encoder.classes_),ignore_mismatched_sizes=True)
model.cuda()  # 默认使用第一张显卡

# 设置优化器和调度器
EPOCHS = 5   # 训练次数,可以先训练5次看看效果,可以自定义修改
optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)   # 优化器
total_steps = len(train_dataloader) * EPOCHS
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps)

# 计算精确度 -- 通过比较预测类别和实际标签的相同之处,计算出预测正确的比例
def flat_accuracy(preds, labels):
    pred_flat = np.argmax(preds, axis=1).flatten()
    labels_flat = labels.flatten()
    return np.sum(pred_flat == labels_flat) / len(labels_flat)

# 训练和评估
print("4、开始训练啦~")
best_val_accuracy = 0
for epoch in range(EPOCHS):
    print(f'Epoch {epoch + 1}')
    now_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())   # 记录每一轮的训练开始时间和结束时间
    print("start time:", now_time)

    model.train()  # 模型设置为训练模式
    total_train_loss = 0  # 初始化训练总损失为0

    for step, batch in enumerate(train_dataloader):
        b_input_ids = batch[0].cuda()
        b_input_mask = batch[1].cuda()
        b_labels = batch[2].cuda().long()

        model.zero_grad() # 清除模型的梯度

        outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)  # 将输入数据传递给模型,得到模型的输出
        loss = outputs.loss # 提取出损失值,用于后续的反向传播
        total_train_loss += loss.item()
        loss.backward() # 进行反向传播,计算梯度
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        scheduler.step()

    avg_train_loss = total_train_loss / len(train_dataloader)  # 更新学习率

    torch.cuda.empty_cache()  # 训练一轮就清空一次显卡缓存
    # 模型测试,计算准确度
    model.eval()
    total_eval_accuracy = 0
    total_eval_loss = 0

    for batch in validation_dataloader:
        b_input_ids = batch[0].cuda()
        b_input_mask = batch[1].cuda()
        b_labels = batch[2].cuda().long()

        with torch.no_grad():
            outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)

        loss = outputs.loss
        total_eval_loss += loss.item()
        logits = outputs.logits
        logits = logits.detach().cpu().numpy()
        label_ids = b_labels.to('cpu').numpy()
        total_eval_accuracy += flat_accuracy(logits, label_ids)

    avg_val_accuracy = total_eval_accuracy / len(validation_dataloader)
    avg_val_loss = total_eval_loss / len(validation_dataloader)

    torch.cuda.empty_cache() # 验证一轮就清空一次显卡缓存

    print(f'Training loss: {avg_train_loss}')
    print(f'Validation loss: {avg_val_loss}')
    print(f'Validation Accuracy: {avg_val_accuracy}')   # 主要看这个精度,一般准确率90%以上就可以投入实际生产环境中

    # 在验证集上计算准确率
    if avg_val_accuracy > best_val_accuracy:
        best_val_accuracy = avg_val_accuracy
        # 保存模型
        model.save_pretrained(best_model_path)   # 根据训练次数,保存最优的一个模型结果

    now_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
    print("end time:",now_time)
    print("-------------------")


三、模型推理

模型训练完成后,现在有一批新数据,你想要使用训练好的模型预测该文本数据的分类结果,则可以使用下面的推理代码,详解看注释。

import pandas as pd
import time
import torch
import joblib
from transformers import BertTokenizer, BertForSequenceClassification
import torch.nn.functional as F

# 第一步:加载数据
file_path = './data/detect.csv'  # 要推理的数据路径
df = pd.read_csv(file_path, encoding='utf-8')

# 第二步:加载训练好的模型
best_model_path = './model'
model = BertForSequenceClassification.from_pretrained(best_model_path)
tokenizer = BertTokenizer.from_pretrained(best_model_path)

# 第三步:加载编码(训练时保存的结果)
label_encoder = joblib.load('./data/encoder.joblib')

predictions = []  # 预测值
confidence_scores = []  # 可信度,一般可信度大于0.9说明效果比较准确

# 第四步:遍历推理数据
for row in df.iterrows():
    content = row[1]['feature']  # 特征列(推理样本)
    inputs = tokenizer(content, return_tensors="pt", padding=True, truncation=True, max_length=256)
    outputs = model(**inputs)
    probs = F.softmax(outputs.logits, dim=1)
    pred = torch.argmax(probs, dim=1)
    confidence = torch.max(probs, dim=1)   # 获取置信度的值

    predictions.append(pred.item())
    confidence_scores.append(confidence.values.item())

# 第五步:将预测结果解码为类别标签
decoded_categories = label_encoder.inverse_transform(predictions)

# 第六步:创建一个空的DataFrame来存储推理结果
df['pred'] = decoded_categories
df['confidence_score'] = confidence_scores

# 将结果保存到本地
output_file_path = './data/detect_pred.csv'  # 保存推理结果的路径
df.to_csv(output_file_path, index=False)

参考文章链接:https://blog.csdn.net/yihong23/article/details/138543746

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

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

相关文章

中国各地级市的海拔标准差

海拔标准差是衡量地理测量准确性的重要指标,它通过计算特定地点的海拔测量值与平均海拔之间的偏差来评估数据的可靠性。较小的标准差意味着测量结果较为一致,而较大的标准差则可能指出数据的波动性或测量误差。 计算方法 海拔标准差的计算遵循以下公式…

科研绘图系列:python语言制标准差的直方图(STD histogram plot)

介绍 密度分布图是一种统计图表,用于表示数据的分布情况。它通常用于展示变量的频率分布,但与直方图不同,密度分布图通过平滑曲线来表示数据的分布,而不是用柱状图来表示。这种图表可以更直观地展示数据的分布形状,如是否对称、是否多峰等。 在密度分布图中,横轴代表数…

如何录制黑神话悟空的游戏BGM导入iPhone手机制作铃声?

在游戏的世界里,总有那么一些旋律,能够触动玩家的心弦,让人难以忘怀。《黑神话悟空》以其精美的画面和动人的背景音乐,赢得了无数玩家的喜爱。如果你也想将游戏中的背景音录制下来,制作成个性化的m4r格式铃声&#xff…

【Go - 每日一小问 ,const 变量存储在哪里,堆还是栈上?】

答:都不是 , 在bss(未初始化数据区) 和 data(初始化数据区)上。 在内存布局上遵循一定规律,Go 进程的内存空间布局由高地址到低地址大致可分为以下几段: 栈(stack): 用户态的栈,栈的大小是固定的,其大小可以使用ulimi…

轿厢电梯-电动车检测数据集(真实电梯监控)

轿厢电动车检测数据集, 可做电梯乘客、电动车检测任务。 数据集由真实电梯监控图片(大约四千)、电动车网图、手机拍摄图片构成,总计14000张左右,其中近8000样本已标注。 注:文件夹后面数字为对应数据集样本…

【C++题解】1241 - 角谷猜想

问题二:1241 - 角谷猜想 类型:有规律的循环、递归。 题目描述: 日本一位中学生发现一个奇妙的定理,请角谷教授证明,而教授无能为力,于是产生了角谷猜想。 猜想的内容:任给一个自然数&#xff…

2024表白墙PHP网站源码

2024表白墙PHP网站源码 正常安装 访问域名即可直接进入安装程序 更新安装 请先备份数据库以及updata文件目录 以防出现意外 以及复制后台“基本配置内容” 然后覆盖目录 访问域名再次进入安装程序 在数据库安装完成后不要进行下一步并删除install文件目录 再将“基本配置内容”…

橘子学ES实战操作之管道类型Ingest pipelines的基本使用

简介 我们在使用ES的时候,经常的用法就是把其他数据源比如Mysql的数据灌到ES中。 借用ES的一些功能来提供数据的全文检索以及聚合分析之类的功能。 在这个灌数据的过程中,我们经常会对数据做一些治理,类似ETL的能力。然后把治理后的数据写入…

List 的介绍

目录 1. 什么是List 2. 常见接口介绍 3. List的使用 1. 什么是List 在集合框架中, List 是一个接口,继承自 Collection 。 Collection 也是一个接口 ,该接口中规范了后序容器中常用的一些方法,具体如下所示: Iterab…

前端学习笔记-Web APIs篇-02

事件监听(绑定) 什么是事件? 事件是在编程时系统内发生的动作或者发生的事情【比如用户在网页上单击一个按钮 】 什么是事件监听? 就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应&#…

3DMAX建筑魔术师MagicBuilding插件使用方法详解

3DMAX建筑魔术师MagicBuilding,一键创建单个或多个随机楼体,可以用来生成建筑场景中的配景楼,让你快速从繁重的体力劳动中解脱出来! 【建议版本】 3dMax2018及以上版本(不仅限于此范围) *以上只是建议版本…

CRM系统为贷款中介行业插上科技的翅膀

CRM(客户关系管理)系统为贷款中介公司插上了科技的翅膀,极大提升了贷款中介企业的运营效率、客户管理能力和市场竞争力。鑫鹿贷款CRM系统基于互联网、大数据分析、人工智能、云计算等前沿技术,帮助贷款中介公司实现业务流程的自动…

Lua中大量注释后取消

在Lua中注释掉一些调试的代码之后,逐个去取消掉又十分耗时麻烦,调试的信息可以像下面这样写, 大量取消的时候可以直接搜索替换。

性能测试⼯具-——JMeter

目录 JMeter介绍下载JMeter的基本使用流程关键组件介绍常见测试场景与分析方法JMeter插件的扩展7. 结论 JMeter介绍 性能测试是软件开发过程中至关重要的一环,尤其是在当今高并发、大数据的应用场景下。性能测试不仅可以帮助开发团队发现系统的瓶颈,还能…

026集——在旧式编码与 Unicode 之间转换(C# 编程指南)——C#学习笔记

在 C# 中,内存中的所有字符串都是按 Unicode (UTF-16) 编码的。将数据从存储器移动到 string 对象中后,数据将自动转换为 UTF-16。如果数据仅包含从 0 到 127 的 ASCII 值,则此转换无需您执行任何额外的工作。但若源文本包含扩展的 ASCII 字节…

检索增强型语言模型——更可靠、可适应、可归因的下一代语言模型

人工智能咨询培训老师叶梓 转载标明出处 传统的参数化语言模型通过大规模的网络数据训练,虽然具备一定的灵活性和能力,但它们在处理具体任务时存在以下主要问题: 事实错误:模型可能会产生与事实不符的信息,即“幻觉”…

OpenCV从入门到精通——角点特征点提取匹配算法实战

harris角点 角点可以是两个边缘的角点;角点是邻域内具有两个主方向的特征点;角点通常被定义为两条边的交点,更严格的说,角点的局部邻域应该具有两个不同区域的不同方向的边界。或者说,角点就是多条轮廓线之间的交点。…

完善补环境框架 bx_et 分析

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 有相关问题请第一时间头像私信联系我…

win10 +git配置+学习笔记

git简介:git是一个分布式版本控制软件,用于有效、高速地处理从小到大的项目版本管理。 安装git:从官网Git (git-scm.com)下载安装包 配置git: git config --global user.name "Your Name" git config --global user.e…

Android 11 (R)AMS Activity内部机制

一、AMS是如何被管理的 如我们在Android 11(R)启动流程中介绍的一样,AMS和ATMS是在SystemServer中被启动的 ActivityTaskManagerService atm mSystemServiceManager.startService(ActivityTaskManagerService.Lifecycle.class).getService(); mActivityManagerSe…