微调篇--超长文本微调训练


分享内容

  1. 新闻评论分析任务训练流程
  2. 加载自定义数据集
  3. 处理超长文本的训练问题
  4. 扩展词汇表并匹配模型
  5. 修改模型配置信息
  6. 评论分析任务概述

评论分析是情感分析的一个应用场景,旨在通过文本分类技术识别评论的情感倾向。本次将详细介绍如何更换数据集和模型来实现这一任务。

训练流程

1.1 数据集更换

在自然语言处理(NLP)任务中,数据集通常包含文本和对应的标签。文章评论分析的数据集可以采用CSV格式,其中一列存储评论文本,另一列存储情感标签(如正面或负面)。

加载CSV格式的数据

我们可以使用datasets库来加载本地的CSV文件。以下是一个示例代码:

python 复制代码
CustomData.py
from torch.utils.data import Dataset  # 从torch库中导入Dataset类,用于创建自定义数据集类
from datasets import load_dataset  # 从datasets库中导入load_dataset函数,用于加载数据集

class CustomDataset(Dataset):  # 定义一个名为MyDataset的类,继承自Dataset类
    def __init__(self,split):  # 定义类的初始化方法,接收一个参数split,表示数据集的分割类型
        #使用load_dataset函数加载csv文件,路径为"./data/{split}.csv",并指定数据集分割为"train"
        self.dataset = load_dataset(path="csv",data_files=f"./data/{split}.csv",split="train")    def __len__(self):  # 定义一个方法用于返回数据集的长度
        return len(self.dataset)  # 返回加载的数据集的长度,即数据集中的样本数量

    def __getitem__(self, item):  # 定义一个方法用于获取数据集中的某个样本
        text = self.dataset[item]["text"]  # 获取数据集中第item个样本的"text"字段
        label = self.dataset[item]["label"]  # 获取数据集中第item个样本的"label"字段

        return text,label  # 返回获取的文本和标签
if name == '__main__':  # 判断是否在主程序中运行
    dataset = CustomDataset("test")  # 创建MyDataset类的实例,传入"test"作为split参数
    for data in dataset:  # 遍历数据集中的每个样本
        print(data)  # 打印每个样本的内容

代码解析

  • load_dataset:用于从磁盘加载数据集,支持多种格式(如CSV、JSON等)。
  • len:返回数据集的大小,便于在训练中使用。
  • getitem:根据索引返回单个数据条目(文本和标签),符合PyTorch的Dataset类标准。

注意事项

  • 数据集路径应根据实际情况调整,如data/Article/{split}.csv
  • 确保CSV文件中的列名与代码中的字段匹配(如"text""label")。

1.2 预训练模型更换

文章评论分析任务可以通过微调预训练的BERT模型来完成。可以选择不同的中文预训练模型,如bert-base-chinesehfl/chinese-roberta-wwm-ext

代码示例

python 复制代码
net.py
from transformers import BertModel, BertConfig  # 导入Bert模型和Bert配置类

import torch  # 导入PyTorch库

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 设置设备为CUDA(如果可用),否则为CPU

pretrained = BertModel.from_pretrained(r"bert-base-chinese").to(DEVICE)
pretrained.embeddings.position_embeddings = torch.nn.Embedding(1500,768).to(DEVICE)
print(pretrained)
config = pretrained.config
configuration = BertConfig.from_pretrained(r"bert-base-chinese")  # 从预训练模型加载配置
configuration.max_position_embeddings = 1500  # 设置最大位置嵌入为1500
print(configuration)  # 打印配置信息
初始化模型
pretrained = BertModel(configuration).to(DEVICE)  # 使用配置初始化Bert模型并移动到指定设备
print(pretrained.embeddings.position_embeddings)  # 打印模型的位置嵌入
print(pretrained)  # 打印整个模型

定义下游任务模型(将主干网络所提取的特征分类)
class Model(torch.nn.Module):  # 定义一个PyTorch模型
    def __init__(self):
        super().__init__()  # 调用父类构造函数
        self.fc = torch.nn.Linear(768, 8)  # 定义一个全连接层,输入768维,输出8维

    # def forward(self,input_ids,attention_mask,token_type_ids):
    #     # with torch.no_grad():
    #     #     out = pretrained(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
    #     out = pretrained(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
    #     out = self.fc(out.last_hidden_state[:,0])
    #     out = out.softmax(dim=1)
    #     return out
    # 调整模型的前向计算,让embeddings部分参与到模型的训练过程(修改了embeddings)
    def forward(self, input_ids, attention_mask, token_type_ids):  # 定义模型的前向传播
        # 让embeddings参与训练
        embeddings_output = pretrained.embeddings(input_ids=input_ids)  # 获取嵌入层的输出
        attention_mask = attention_mask.to(torch.float)  # 将注意力掩码转换为浮点类型
        # 将数据形状转换为[N,1,1,sequence_length]使其匹配attention层的输入形状
        attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)  # 增加两个维度
        attention_mask = attention_mask.to(embeddings_output.dtype)  # 将注意力掩码的数据类型与嵌入层输出对齐
        # 冻结encoder和pooler,使用torch.no_grade()节省显存
        with torch.no_grad():  # 在不计算梯度的情况下执行
            encoder_output = pretrained.encoder(embeddings_output, attention_mask=attention_mask)  # 获取编码器的输出
        out = self.fc(encoder_output.last_hidden_state[:, 0])  # 使用全连接层处理编码器的最后一个隐藏状态
        return out  # 返回输出

代码解析

  • BertModel.from_pretrained():加载Hugging Face提供的预训练BERT模型。
  • with torch.no_grad():冻结BERT模型的权重,只训练下游任务的全连接层。
  • self.fc:定义一个全连接层,用于将BERT的输出映射到分类结果。
  • last_hidden_state[:, 0]:提取BERT的[CLS]标记对应的向量,通常用于分类任务的最终输出。

注意事项

  • 如果任务是二分类(如情感分析),请将self.fc = torch.nn.Linear(768, 2)进行调整。

1.3 训练与测试

训练是微调模型的关键步骤。我们使用DataLoader来批量加载数据,并使用AdamW作为优化器来进行模型参数的优化。

训练代码

ini 复制代码
trainer.py
import torch  # 导入PyTorch库
from CustomData import CustomDataset  # 导入自定义数据集类
from torch.utils.data import DataLoader  # 导入PyTorch的数据加载器
from net import Model  # 导入自定义的模型类
from transformers import AdamW, BertTokenizer  # 导入AdamW优化器和Bert分词器

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 设置设备为CUDA(如果可用),否则为CPU
EPOCH = 1  # 设置训练的总轮数

token = BertTokenizer.from_pretrained(r"bert-base-chinese")  # 加载预训练的Bert分词器

def collate_fn(data):
    sentes = [i[0] for i in data]  # 提取数据集中的句子
    label = [i[1] for i in data]  # 提取数据集中的标签
    # print(sentes)
    # 编码句子
    data = token.batch_encode_plus(batch_text_or_text_pairs=sentes,  # 使用BertTokenizer对句子进行批量编码,类似于将句子转换为模型可以理解的数字形式
                                   truncation=True,  # 启用截断功能,确保每个句子不会超过指定的最大长度
                                   padding="max_length",  # 使用最大长度进行填充,确保所有句子长度一致
                                   max_length=1500,  # 设置最大长度为1500,超过的部分会被截断,未达到的部分会被填充
                                   return_tensors="pt",  # 返回PyTorch张量格式的数据,方便后续在模型中使用
                                   return_length=True)  # 返回每个句子的实际长度,便于后续处理
    input_ids = data["input_ids"]  # 获取输入ID,这些ID是词汇表中每个词的唯一标识符
    attention_mask = data["attention_mask"]  # 获取注意力掩码,用于指示哪些词是填充的,哪些是实际内容
    token_type_ids = data["token_type_ids"]  # 获取token类型ID,用于区分句子对中的不同句子
    labels = torch.LongTensor(label)  # 将标签转换为LongTensor格式,便于在PyTorch中进行计算
    # print(input_ids,attention_mask,token_type_ids)
    return input_ids, attention_mask, token_type_ids, labels  # 返回处理后的数据,包括输入ID、注意力掩码、token类型ID和标签

创建数据集
train_dataset = CustomDataset("train")  # 创建训练数据集,类似于准备好一组用于训练的样本
val_dataset = CustomDataset("validation")  # 创建验证数据集,用于在训练过程中评估模型性能
创建数据加载器
train_laoder = DataLoader(dataset=train_dataset,  # 使用DataLoader为训练数据集创建数据加载器,便于批量处理数据
                          batch_size=1,  # 设置每个批次的大小为1,即每次只处理一个样本
                          shuffle=True,  # 启用随机打乱功能,确保每次训练时数据顺序不同,增加模型的泛化能力
                          drop_last=True,  # 如果最后一个批次的数据量不足,则丢弃该批次,确保每个批次的数据量一致
                          collate_fn=collate_fn)  # 使用自定义的collate_fn函数来处理每个批次的数据

if name == '__main__':
    # 开始训练
    print(DEVICE)  # 打印使用的设备,告诉用户当前使用的是CPU还是GPU
    model = Model().to(DEVICE)  # 初始化模型并将其移动到指定设备上,确保计算在合适的硬件上进行
    optimizer = AdamW(model.parameters(), lr=5e-4)  # 使用AdamW优化器,设置学习率为5e-4,优化器用于更新模型参数
    loss_func = torch.nn.CrossEntropyLoss()  # 使用交叉熵损失函数,常用于分类问题中计算预测值与真实值之间的差异

    model.train()  # 设置模型为训练模式,启用dropout等训练时特有的功能
    for epoch in range(EPOCH):  # 训练EPOCH轮,EPOCH表示完整遍历一次训练数据集
        sum_val_acc = 0  # 初始化验证准确率的累加器
        sum_val_loss = 0  # 初始化验证损失的累加器
        # 训练
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_laoder):  # 遍历训练数据集
            # print(input_ids)
            input_ids, attention_mask, token_type_ids, labels = input_ids.to(DEVICE), attention_mask.to(
                DEVICE), token_type_ids.to(DEVICE), labels.to(DEVICE)  # 将数据移动到指定设备上
            out = model(input_ids, attention_mask, token_type_ids)  # 前向传播,计算模型的输出

            loss = loss_func(out, labels)  # 计算损失,衡量模型输出与真实标签之间的差异
            optimizer.zero_grad()  # 清空优化器的梯度,防止梯度累积
            loss.backward()  # 反向传播,计算梯度
            optimizer.step()  # 更新模型参数,根据计算出的梯度调整参数值

            if i % 5 == 0:  # 每5个批次打印一次信息,便于观察训练过程
                out = out.argmax(dim=1)  # 获取预测结果,选择概率最大的类别
                acc = (out == labels).sum().item() / len(labels)  # 计算准确率,预测正确的样本数除以总样本数
                print(epoch, i, loss.item(), acc)  # 打印当前轮数、批次、损失和准确率

        torch.save(model.state_dict(), f"model/{epoch}-bert.pth")  # 保存模型参数到文件,便于后续加载和使用
        print(epoch, "参数保存成功!")  # 打印成功信息,告知用户模型参数已保存

代码解析

  • DataLoader:用于批量加载数据,每次加载batch_size=100的样本并随机打乱数据。

  • BertTokenizer:用于将文本转换为模型可处理的token。

  • AdamW:适合BERT等大型语言模型的优化器,结合了Adam和权重衰减,防止过拟合。

变量说明

在自然语言处理(NLP)任务中,input_idsattention_masktoken_type_ids 是使用预训练的BERT模型进行文本处理时常用的输入格式。让我们逐一解释这些变量的作用,并通过一个简单的例子来说明。

  1. input_ids:

    1. 作用 : input_ids 是文本经过分词器(如BertTokenizer)处理后得到的词汇表索引。每个单词或子词被映射到一个唯一的整数ID。
    2. 例子: 假设我们有一个句子 "我爱编程"。分词器可能会将其分成 ["我", "爱", "编", "程"],并将每个词映射到相应的ID,比如 [101, 1001, 2001, 3001]。
  2. attention_mask:

    1. 作用 : attention_mask 用于指示模型在处理输入时应该关注哪些词。值为1的地方表示模型应该关注,值为0的地方表示模型应该忽略(通常是填充的部分)。
    2. 例子 : 如果句子 "我爱编程" 被填充到最大长度5,可能会变成 ["我", "爱", "编", "程", "[PAD]"],对应的 attention_mask 为 [1, 1, 1, 1, 0]。
  3. token_type_ids:

    1. 作用 : token_type_ids 用于区分不同的句子对(如句子A和句子B)在输入中。对于单个句子输入,通常全为0。
    2. 例子 : 在句子对任务中,比如句子A是 "我爱编程",句子B是 "编程很有趣",token_type_ids 可能是 [0, 0, 0, 0, 1, 1, 1, 1],表示前四个词属于句子A,后四个词属于句子B。
  4. labels:

    1. 作用 : labels 是目标标签,用于监督学习中的损失计算。通常是一个整数,表示类别。
    2. 例子: 如果我们在做情感分析,句子 "我爱编程" 的标签可能是1(表示正面情感)。

这些变量共同作用,使得模型能够有效地理解和处理输入文本。通过这种结构化的输入,BERT模型可以更好地捕捉文本中的语义信息。

注意事项

  • max_length=500用于限制文本长度,防止输入过长导致内存占用过大。

2. 资讯评论数据集介绍

资讯评论是NLP中的经典任务,通常需要对评论文本进行分类,将其归入不同的类别。

2.1 加载Hugging Face的资讯评论数据集

我们可以使用Hugging Face的datasets库来加载资讯评论数据集,如THUCNews。这是一个中文资讯评论数据集,适合用于文本分类任务。

2.2 加载自定义CSV数据集

如果有自定义的资讯评论数据集,可以将其保存为CSV文件,并通过datasets库加载。

代码示例

数据集地址:huggingface.co/datasets/se...

kotlin 复制代码
data.py
from datasets import load_dataset

加载Hugging Face上的THUCNews数据集
data = load_dataset(path="seamew/THUCNewsText", split="train")
print(data)

遍历数据集查看样本
for i in data:
    print(i)

data = load_dataset(path="csv", data_files="data/train.csv", split="train")

注意事项

  • 确保CSV文件的格式正确,列名要与代码中的字段匹配,如"text""label"

22

3.处理超长文本的训练问题

BERT模型的最大输入长度为512个token,但在实际应用中,文本长度可能会超过该限制,导致信息丢失或无法输入模型。

3.1 文本截断

对于长度超过最大输入长度的文本,可以通过截断的方式保留前512个token。

3.2 增加max_position_embeddings

如果文本长度远远超过512个token,可以通过调整BERT模型的配置来增加max_position_embeddings,使模型支持更长的输入。

代码示例

ini 复制代码
from transformers import BertConfig, BertModel

# 修改max_position_embeddings参数
configuration = BertConfig.from_pretrained("yechen/bert-large-chinese")
configuration.max_position_embeddings = 1500  # 增加最大输入长度
model = BertModel(configuration)

注意事项

  • 增加max_position_embeddings需要更多的计算资源,输入的长度越大,计算量和内存需求越高。

4.扩展词汇表并匹配模型

在实际应用中,数据集中可能会出现未包含在预训练模型词汇表中的新词汇。这时我们需要扩展模型的vocab

4.1 添加新词汇到词汇表

通过BertTokenizeradd_tokens方法添加新词汇:

ini 复制代码
token = BertTokenizer.from_pretrained("yechen/bert-large-chinese")
token.add_tokens(["新词汇1", "新词汇2"])

4.2 调整模型的嵌入层

添加新词汇后,需要调整模型的嵌入层,以便模型能够处理新的词汇。

代码示例

model.resize_token_embeddings(len(token))

注意事项

  • add_tokens:用于向现有的词汇表中添加新词汇。
  • resize_token_embeddings:调整模型的嵌入层大小,以匹配扩展后的词汇表。

5.修改模型配置信息

模型配置(如max_position_embeddingshidden_size)可以根据任务需求进行调整,特别是在处理不同长度的文本或需要调整模型容量时。

5.1 修改模型配置

通过BertConfig可以定制化模型配置。

代码示例

ini 复制代码
from transformers import BertConfig, BertModel

# 自定义BERT配置
configuration = BertConfig.from_pretrained("yechen/bert-large-chinese")
configuration.max_position_embeddings = 1500  # 修改最大输入长度
configuration.hidden_size = 1024  # 修改隐藏层大小

# 使用自定义配置初始化模型
model = BertModel(configuration)

注意事项

  • 修改配置时要确保与实际任务的需求匹配,同时要考虑计算资源的限制。
相关推荐
AntBlack22 分钟前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端
leo__52035 分钟前
matlab实现非线性Granger因果检验
人工智能·算法·matlab
struggle202535 分钟前
Burn 开源程序是下一代深度学习框架,在灵活性、效率和可移植性方面毫不妥协
人工智能·python·深度学习·rust
CareyWYR1 小时前
每周AI论文速递(2506209-250613)
人工智能
MYH5161 小时前
无监督的预训练和有监督任务的微调
人工智能
Jet45052 小时前
玩转ChatGPT:DeepSeek实战(核酸蛋白序列核对)
人工智能·chatgpt·kimi·deepseek
几夏经秋2 小时前
图文教程——Deepseek最强平替工具免费申请教程——国内edu邮箱可用
人工智能
中國龍在廣州2 小时前
AI首次自主发现人工生命
人工智能·科技·机器学习·机器人
I-NullMoneyException3 小时前
智能语音交互技术深度解析:从原理到产业实践
人工智能
创小匠3 小时前
创客匠人:AI重构知识IP定位与变现效率新范式
人工智能·tcp/ip·重构