分享内容
- 新闻评论分析任务训练流程
- 加载自定义数据集
- 处理超长文本的训练问题
- 扩展词汇表并匹配模型
- 修改模型配置信息
- 评论分析任务概述
评论分析是情感分析的一个应用场景,旨在通过文本分类技术识别评论的情感倾向。本次将详细介绍如何更换数据集和模型来实现这一任务。
训练流程

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-chinese
或hfl/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_ids
、attention_mask
和 token_type_ids
是使用预训练的BERT模型进行文本处理时常用的输入格式。让我们逐一解释这些变量的作用,并通过一个简单的例子来说明。
-
input_ids
:- 作用 :
input_ids
是文本经过分词器(如BertTokenizer)处理后得到的词汇表索引。每个单词或子词被映射到一个唯一的整数ID。 - 例子: 假设我们有一个句子 "我爱编程"。分词器可能会将其分成 ["我", "爱", "编", "程"],并将每个词映射到相应的ID,比如 [101, 1001, 2001, 3001]。
- 作用 :
-
attention_mask
:- 作用 :
attention_mask
用于指示模型在处理输入时应该关注哪些词。值为1的地方表示模型应该关注,值为0的地方表示模型应该忽略(通常是填充的部分)。 - 例子 : 如果句子 "我爱编程" 被填充到最大长度5,可能会变成 ["我", "爱", "编", "程", "[PAD]"],对应的
attention_mask
为 [1, 1, 1, 1, 0]。
- 作用 :
-
token_type_ids
:- 作用 :
token_type_ids
用于区分不同的句子对(如句子A和句子B)在输入中。对于单个句子输入,通常全为0。 - 例子 : 在句子对任务中,比如句子A是 "我爱编程",句子B是 "编程很有趣",
token_type_ids
可能是 [0, 0, 0, 0, 1, 1, 1, 1],表示前四个词属于句子A,后四个词属于句子B。
- 作用 :
-
labels
:- 作用 :
labels
是目标标签,用于监督学习中的损失计算。通常是一个整数,表示类别。 - 例子: 如果我们在做情感分析,句子 "我爱编程" 的标签可能是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 添加新词汇到词汇表
通过BertTokenizer
的add_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_embeddings
和hidden_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)
注意事项
- 修改配置时要确保与实际任务的需求匹配,同时要考虑计算资源的限制。