0%

大模型应用系列(四) GPT2的调用和微调,以及数据集的制作

在古诗词数据集上微调GPT2,介绍了如何调用GPT,如何制作数据集,以及如何微调。

一. GPT2中文模型的推理调用

1.1 下载GPT2模型

具体下载方法见LLM应用第一篇

从HF-mirror下载gpt2-chinese-cluecorpussmall

1.2 使用transformers调用模型生成文本

主要步骤有: 加载模型和分词器,构建模型生成Pipeline, 调用模型生成文本。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 中文白话文生成
from transformers import GPT2LMHeadModel, BertTokenizer, TextGenerationPipeline

# 加载模型和分词器
# 模型路径要到config.json所在目录
model = GPT2LMHeadModel.from_pretrained('')
tokenizer = BertTokenizer.from_pretrained('')

print(model)

# 使用Pipeline调用模型
text_generator = TextGenerationPipeline(model, tokenizer, device='cpu') # 如果有cuda,则写cuda

# 生成文本
# max_length控制生成长度, do_sample=True表示进行随机采样,每次生成的结果都不一样,为False时,每次生成的结果都一样
for i in range(3):
print(text_generator("这是很久之前的事情了,", max_length=100, do_sample=True))

运行结果如下:因为开启了do_sample, 所以三次生成的文本都不同。

image-20250313223231958

二. 本地训练GPT2中文模型

2.1 准备数据

准备想要GPT生成的语料数据,比如中文诗词,不需要太多的标注。

数据集以及代码可以在 微调GPT中文生成模型,生成古诗风格资源-CSDN文库获取

2.2 构造数据类

数据类构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from torch.utils.data import Dataset

class MyDataset(Dataset):
def __init__(self):
with open(file='./data/chinese_poems.txt', encoding='utf-8') as file:
lines = file.readlines()

lines = [i.strip() for i in lines]
self.lines = lines

def __len__(self):
return len(self.lines)

def __getitem__(self, item):
return self.lines[item]

if __name__ == "__main__":
dataset = MyDataset()
for data in dataset:
print(data)
2.3 训练模型

train.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from transformers import AdamW
from transformers.optimization import get_scheduler
import torch
from data import MyDataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from torch.utils.data import DataLoader

# 实例化数据集
dataset = MyDataset()

# 加载模型和分词器
model = AutoModelForCausalLM.from_pretrained('gpt2-chinese-cluecorpussmall')
tokenizer = AutoTokenizer.from_pretrained('gpt2-chinese-cluecorpussmall')

# 将传入的字符串进行编码
def collate_fn(data):

# 编码
data = tokenizer.batch_encode_plus(
batch_text_or_text_pairs=data, # 要编码的句子
padding=True, # 填充
truncation=True, # 截断超过max_length的部分
max_length=512, # 最大长度
return_tensors="pt", # 返回的数组是什么类型,tf(Tensorflow), pt(pytorch张量), np(numpy), None(list)
)
# 把输入复制为标签
data['labels'] = data['input_ids'].clone()
return data

# 使用dataLoader创建数据加载器, 用于批量加载数据
data_loader = DataLoader(
dataset=dataset,
batch_size=2,
shuffle=True,
drop_last=True,
collate_fn=collate_fn
)
print(len(data_loader))

def train():
# 训练参数
EPOCH = 3000
global model # 使用全局变量
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(DEVICE)

# 定义优化器
optimizer = AdamW(model.parameters(), lr=2e-5)

# 定义学习率调度器
scheduler = get_scheduler(name='linear', # 线性调度器
num_warmup_steps=0, # 预测步数
num_training_steps=len(data_loader), # 训练步数
optimizer=optimizer)
model.train() # 模型开启训练模式
for epoch in range(EPOCH):
for i, data in enumerate(data_loader): # 遍历数据
for k in data.keys():
data[k] = data[k].to(DEVICE)

out = model(**data) # 前向计算
loss = out['loss'] # 获取损失

loss.backward() # 后向传播
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # 梯度裁剪,防止梯度爆炸
optimizer.step() # 更新模型参数
scheduler.step() # 更新学习率
optimizer.zero_grad()
model.zero_grad()

if i % 50 == 0: # 每隔50个批次打印一次信息
labels = data["labels"][:, 1:] # 获取真实标签,忽略<bos>标记
out = out["logits"].argmax(dim=2)[:, :-1] # 获取预测结果,忽略<eos>标记

select = labels != 0 # 选择非填充的标签
labels = labels[select] # 应用选择
out = out[select] # 应用选择
del select # 删除不再使用的select
# 计算准确率
acc = (labels == out).sum().item() / labels.numel() # 计算准确率的公式
lr = optimizer.state_dict()["param_groups"][0]['lr'] # 获取当前学习率

# 打印训练信息
print(f"epoch:{epoch},batch:{i},loss:{loss.item()},lr:{lr},acc:{acc}")

# 保存最后一轮模型参数
torch.save(model.state_dict(), "params/net.pt") # 保存模型参数到指定路径
print("权重保存成功!") # 打印成功信息

# 当该脚本作为主程序运行时,调用训练函数
if __name__ == '__main__':
train() # 开始训练过程

三. 补充

3.1 生成模型的训练数据

训练的数据不含标签, 只含文本, 文本就是标签,模型根据前面生成下一个字,然后把后面的文本作为标签,计算loss。

如果您读文章后有收获,可以打赏我喝咖啡哦~