Ai大模型第二课:以多分类问题实战梳理增量微调
多分类的增量模型定义和二分类很类似,在全连接网络进行修改就行了。对多分类问题进行处理后,就能对增量微调形成一套自己的理解,这是最基础的知识,但也算是对Ai大模型的学习踏入门槛了。对于各位初学者而言,需要自行进行学习和训练,相信各位一定能很快轻松上手。
目录
1.准备工作
在上一课中,我们对深度学习和增量微调有了基本的认识,在进行模型的训练之前,我们要进行思路理清。
1.1明确任务目标
首先,我们要确认任务类型,比如:
- 分类(如猫狗识别)
- 回归(如房价预测)
- 生成(如文本续写)
根据任务类型设定好评估指标,比如:
- 分类任务:准确率(Accuracy)
- 回归任务:均方误差(MSE)
- 生成任务:人工主观评估(初期可跳过复杂指标)
对资源进行评估,对于初学者而言,硬件设备是一大难点,要根据自身条件合理选取模型和任务,对于模型训练而言,GPu是必不可少的,CPU进行模型训练的效率十分之差,想了解原因的可以自行搜索。
那么在这里,我们明确任务类型是:多分类问题,多分类的评估指标需在原有准确率计算的基础上增加 混淆矩阵(Confusion Matrix) 和 分类报告(Classification Report)。资源评估方面,笔者目前租用的3080Ti云服务器在基础模型训练上可以胜任。
1.2数据集准备
目前网络上公开的数据集有两个主要的来源:
一是官方社区所提供的可以直接下载的数据集。
二是自己完成对数据集的爬取。
这里笔者选择从官方社区进行下载,目前的ai生态社区主要有Hugging Face(国外),modelscope(国内)。
我们从modelscope中搜索新闻文本标题分类相关的数据集,完成下载。
1.3预训练模型选择
基础的分类问题,我们直接选择BERT模型就可以胜任,可以选择Hugging Face直接提供的BERT模型,也可以自行选择下载。
2.数据集预处理
对于网络上直接下载的数据集或是爬取的数据,显然是不能直接使用的,需要对数据集进行预处理。
对于我们下载的新闻分类数据集,数据量过大,存在14个不同的新闻分类,对于初学者而言,显然是没有必要完成太多的数据训练,浪费时间和算力。
2.1数据清洗
我们自行对数据进行截取,只保留前七个,进行七分类问题的实战。

我们打开数据集后可以看到格式,每行有三段数据,分别是labels,categories,texts,我们通过python代码将其转化为datasets对象。
from datasets import Dataset,DatasetDict
# 初始化存储数据的列表
labels = []
categories = []
texts = []
# 读取并处理文件
with open('新闻分类数据集.txt', 'r', encoding='utf-8') as f:
for line in f:
# 分割每行的三个字段(标签、分类、文本)
parts = line.strip().split('\t', 2)
if len(parts) != 3:
print(f"跳过格式错误的行: {line}")
continue
label, category, text = parts
labels.append(int(label)) # 转换为整数标签
categories.append(category)
texts.append(text)
# 构建数据集字典
dataset_dict = {
'label': labels,
'category': categories,
'text': texts
}
# 创建Dataset对象
dataset = Dataset.from_dict(dataset_dict)
那么现在有了一个数据集对象,但我们知道模型训练中是存在训练集和测试集的,部分还存在验证集,比例通常为7:2(验证):1(测试)。
dataset = dataset.remove_columns("category")
# 第一次划分:70% 训练集,30% 临时集(后续用于验证和测试)
split_temp = dataset.train_test_split(
test_size=0.3, # 临时集占 30%
shuffle=True, # 打乱数据
seed=42 # 随机种子(保证可复现)
)
train_dataset = split_temp["train"] # 训练集(70%)
temp_dataset = split_temp["test"] # 临时集(30%)
# 第二次划分:将临时集按 2:1 分为验证集和测试集
split_val_test = temp_dataset.train_test_split(
test_size=0.333, # 测试集占临时集的 1/3(即总数据 10%)
shuffle=True,
seed=42
)
val_dataset = split_val_test["train"] # 验证集(20%)
test_dataset = split_val_test["test"] # 测试集(10%)
print(f"训练集样本数: {len(train_dataset)}") # 应≈总数据 70%
print(f"验证集样本数: {len(val_dataset)}") # 应≈总数据 20%
print(f"测试集样本数: {len(test_dataset)}") # 应≈总数据 10%
# 构建 DatasetDict
dataset_dict_ = DatasetDict({
"train": train_dataset,
"validation": val_dataset,
"test": test_dataset
})
# 查看结构
print(dataset_dict_)
# 验证数据集(可选)
print(dataset)
print(dataset[0]) # 查看第一条样本
dataset_dict_.save_to_disk(r'./data/news')
在多分类问题中,类别是可以蕴含在label里的,所以可以从数据集中删除类别。
在划分训练集,验证集,测试集后,再构成一个完整的dataset对象,最后进行保存。
对于保存后的数据集,我们还要对dataset_info.json进行一定的修改,保证label的格式与标题进行对应。

2.2数据预处理
from torch.utils.data import Dataset
from datasets import load_from_disk
class MyDataset(Dataset):
#初始化数据集
def __init__(self,split):
#从磁盘加载数据
self.dataset = load_from_disk(r"/home/ubuntu/demo/demo_01/data/news")
if split == "train":
self.dataset = self.dataset["train"]
elif split == "test":
self.dataset = self.dataset["test"]
elif split == "validation":
self.dataset = self.dataset["validation"]
else:
print("数据名错误!")
#返回数据集长度
def __len__(self):
return len(self.dataset)
#对每条数据单独做处理
def __getitem__(self, item):
text = self.dataset[item]["text"]
label = self.dataset[item]["label"]
return text,label
if __name__ == '__main__':
dataset = MyDataset("test")
for data in dataset:
print(data)
3.模型定义
多分类的增量模型定义和二分类很类似,在全连接网络进行修改就行了。
import torch
from transformers import BertModel
#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#加载预训练模型
pretrained = BertModel.from_pretrained(r"/home/ubuntu/demo/demo_01/model/bert-base-chinese/models--bert-base-chinese/snapshots/c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f").to(DEVICE)
#定义下游任务(增量模型)
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
#设计全连接网络,实现七分类任务
self.fc = torch.nn.Linear(768,7)
#使用模型处理数据(执行前向计算)
def forward(self,input_ids,attention_mask,token_type_ids):
#冻结Bert模型的参数,让其不参与训练
with torch.no_grad():
out = pretrained(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
#增量模型参与训练
out = self.fc(out.last_hidden_state[:,0])
return out
4.模型训练
#模型训练
import torch
from MyData import MyDataset
from torch.utils.data import DataLoader
from net import Model
from transformers import BertTokenizer
from torch.optim import AdamW
#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#定义训练的轮次(将整个数据集训练完一次为一轮)
EPOCH = 3000
#加载字典和分词器
token = BertTokenizer.from_pretrained(r"/home/ubuntu/demo/demo_01/model/bert-base-chinese/models--bert-base-chinese/snapshots/c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f")
#将传入的字符串进行编码
def collate_fn(data):
sents = [i[0]for i in data]
label = [i[1] for i in data]
#编码
data = token.batch_encode_plus(
batch_text_or_text_pairs=sents,
# 当句子长度大于max_length(上限是model_max_length)时,截断
truncation=True,
max_length=512,
# 一律补0到max_length
padding="max_length",
# 可取值为tf,pt,np,默认为list
return_tensors="pt",
# 返回序列长度
return_length=True
)
input_ids = data["input_ids"]
attention_mask = data["attention_mask"]
token_type_ids = data["token_type_ids"]
label = torch.LongTensor(label)
return input_ids,attention_mask,token_type_ids,label
#创建数据集
train_dataset = MyDataset("train")
train_loader = DataLoader(
dataset=train_dataset,
#训练批次
batch_size=50,
#打乱数据集
shuffle=True,
#舍弃最后一个批次的数据,防止形状出错
drop_last=True,
#对加载的数据进行编码
collate_fn=collate_fn
)
#创建验证数据集
val_dataset = MyDataset("validation")
val_loader = DataLoader(
dataset=val_dataset,
#训练批次
batch_size=50,
#打乱数据集
shuffle=True,
#舍弃最后一个批次的数据,防止形状出错
drop_last=True,
#对加载的数据进行编码
collate_fn=collate_fn
)
if __name__ == '__main__':
#开始训练
print(DEVICE)
model = Model().to(DEVICE)
#定义优化器
optimizer = AdamW(model.parameters())
#定义损失函数
loss_func = torch.nn.CrossEntropyLoss()
#初始化验证最佳准确率
best_val_acc = 0.0
for epoch in range(EPOCH):
for i,(input_ids,attention_mask,token_type_ids,label) in enumerate(train_loader):
#将数据放到DVEVICE上面
input_ids, attention_mask, token_type_ids, label = input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE),label.to(DEVICE)
#前向计算(将数据输入模型得到输出)
out = model(input_ids,attention_mask,token_type_ids)
#根据输出计算损失
loss = loss_func(out,label)
#根据误差优化参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
#每隔200个批次输出训练信息
if i%200 ==0:
out = out.argmax(dim=1)
#计算训练精度
acc = (out==label).sum().item()/len(label)
print(f"epoch:{epoch},i:{i},loss:{loss.item()},acc:{acc}")
#验证模型(判断模型是否过拟合)
#设置为评估模型
model.eval()
#不需要模型参与训练
with torch.no_grad():
val_acc = 0.0
val_loss = 0.0
for i, (input_ids, attention_mask, token_type_ids, label) in enumerate(val_loader):
# 将数据放到DVEVICE上面
input_ids, attention_mask, token_type_ids, label = input_ids.to(DEVICE), attention_mask.to(
DEVICE), token_type_ids.to(DEVICE), label.to(DEVICE)
# 前向计算(将数据输入模型得到输出)
out = model(input_ids, attention_mask, token_type_ids)
# 根据输出计算损失
val_loss += loss_func(out, label)
#根据数据,计算验证精度
out = out.argmax(dim=1)
val_acc+=(out==label).sum().item()
val_loss/=len(val_loader)
val_acc/=len(val_loader)
print(f"验证集:loss:{val_loss},acc:{val_acc}")
# #每训练完一轮,保存一次参数
# torch.save(model.state_dict(),f"params/{epoch}_bert.pth")
# print(epoch,"参数保存成功!")
#根据验证准确率保存最优参数
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(),"params1/best_bert.pth")
print(f"EPOCH:{epoch}:保存最优参数:acc{best_val_acc}")
#保存最后一轮参数
torch.save(model.state_dict(), "params1/last_bert.pth")
print(f"EPOCH:{epoch}:最后一轮参数保存成功!")
# if epoch%20 == 0 and epoch!=0:
# #平均一轮半小时,定时二十个轮
# exit()
5.模型评估与预测
5.1模型评估
#模型评估
import torch
from MyData import MyDataset
from torch.utils.data import DataLoader
from net import Model
from transformers import BertTokenizer
from torch.optim import AdamW
import numpy as np
from sklearn.metrics import classification_report, confusion_matrix
#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#加载字典和分词器
token = BertTokenizer.from_pretrained(r"/home/ubuntu/demo/demo_03/model/bert-base-chinese/models--bert-base-chinese/snapshots/c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f")
#将传入的字符串进行编码
def collate_fn(data):
sents = [i[0]for i in data]
label = [i[1] for i in data]
#编码
data = token.batch_encode_plus(
batch_text_or_text_pairs=sents,
# 当句子长度大于max_length(上限是model_max_length)时,截断
truncation=True,
max_length=512,
# 一律补0到max_length
padding="max_length",
# 可取值为tf,pt,np,默认为list
return_tensors="pt",
# 返回序列长度
return_length=True
)
input_ids = data["input_ids"]
attention_mask = data["attention_mask"]
token_type_ids = data["token_type_ids"]
label = torch.LongTensor(label)
return input_ids,attention_mask,token_type_ids,label
#创建数据集
test_dataset = MyDataset("test")
test_loader = DataLoader(
dataset=test_dataset,
#训练批次
batch_size=100,
#打乱数据集
shuffle=True,
#舍弃最后一个批次的数据,防止形状出错
drop_last=True,
#对加载的数据进行编码
collate_fn=collate_fn
)
if __name__ == '__main__':
# acc = 0.0
# total = 0
#开始测试
print(DEVICE)
model = Model().to(DEVICE)
#加载模型训练参数
model.load_state_dict(torch.load(r"/home/ubuntu/demo/demo_01/params1/best_bert.pth"))
#开启测试模式
model.eval()
# 收集所有预测结果和真实标签
all_preds = []
all_labels = []
with torch.no_grad():
for input_ids, attention_mask, token_type_ids, label in test_loader:
input_ids = input_ids.to(DEVICE)
attention_mask = attention_mask.to(DEVICE)
token_type_ids = token_type_ids.to(DEVICE)
label = label.to(DEVICE)
# 前向计算
out = model(input_ids, attention_mask, token_type_ids)
preds = out.argmax(dim=1)
# 收集结果
all_preds.extend(preds.cpu().numpy()) # 转移到CPU并转为numpy数组
all_labels.extend(label.cpu().numpy())
# 转换为 numpy 数组
all_preds = np.array(all_preds)
all_labels = np.array(all_labels)
# 计算整体准确率
accuracy = (all_preds == all_labels).sum() / len(all_labels)
print(f"整体准确率: {accuracy:.4f}\n")
# 生成混淆矩阵
cm = confusion_matrix(all_labels, all_preds)
print("混淆矩阵:")
print(cm, "\n")
# 生成分类报告(包含精确率、召回率、F1值)
class_names = ["财经", "彩票", "房产", "股票", "家居", "教育", "科技"] # 替换为实际类别名称
report = classification_report(
all_labels,
all_preds,
target_names=class_names,
digits=4
)
print("分类报告:")
print(report)
5.2模型预测
#模型使用接口(主观评估)
#模型训练
import torch
from net import Model
from transformers import BertTokenizer
#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#加载字典和分词器
token = BertTokenizer.from_pretrained(r"/home/ubuntu/demo/demo_01/model/bert-base-chinese/models--bert-base-chinese/snapshots/c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f")
model = Model().to(DEVICE)
names = ["财经", "彩票", "房产", "股票", "家居", "教育", "科技"]
#将传入的字符串进行编码
def collate_fn(data):
sents = []
sents.append(data)
#编码
data = token.batch_encode_plus(
batch_text_or_text_pairs=sents,
# 当句子长度大于max_length(上限是model_max_length)时,截断
truncation=True,
max_length=512,
# 一律补0到max_length
padding="max_length",
# 可取值为tf,pt,np,默认为list
return_tensors="pt",
# 返回序列长度
return_length=True
)
input_ids = data["input_ids"]
attention_mask = data["attention_mask"]
token_type_ids = data["token_type_ids"]
return input_ids,attention_mask,token_type_ids
def test():
#加载模型训练参数
model.load_state_dict(torch.load(r"/home/ubuntu/demo/demo_01/params1/best_bert.pth"))
#开启测试模型
model.eval()
while True:
data = input("请输入测试数据(输入‘q’退出):")
if data=='q':
print("测试结束")
break
input_ids,attention_mask,token_type_ids = collate_fn(data)
input_ids, attention_mask, token_type_ids = input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE)
#将数据输入到模型,得到输出
with torch.no_grad():
out = model(input_ids,attention_mask,token_type_ids)
out = out.argmax(dim=1)
print("模型判定:",names[out],"\n")
if __name__ == '__main__':
test()
6.总结
对多分类问题进行处理后,就能对增量微调形成一套自己的理解,这是最基础的知识,但也算是对Ai大模型的学习踏入门槛了。对于各位初学者而言,需要自行进行学习和训练,相信各位一定能很快轻松上手。
更多推荐
所有评论(0)