二十、使用PyTorch和Hugging Face Transformers训练中文GPT-2模型的技术实践

BERT情感分析训练训练的是下游任务,gpt2训练的是整体的模型

一、引言

随着大语言模型技术的快速发展,基于 Transformer 架构的模型在自然语言处理任务中展现出卓越的性能。GPT2 作为 OpenAI 推出的生成式预训练语言模型,凭借其强大的上下文建模能力,成为中文文本生成任务的优选方案之一。本文将以中文诗歌生成为例,详细讲解如何基于 Hugging Face Transformers 库实现 GPT2 模型的微调训练,从数据处理、模型加载到训练流程搭建,完整还原实战过程,并整合核心代码文件的实现细节。

二、环境准备

在开始代码实战前,需搭建对应的开发环境,核心依赖库包括 PyTorch(深度学习框架)、Transformers(Hugging Face 提供的预训练模型工具库)。执行以下命令完成安装:

复制代码
pip install torch transformers

三、数据处理:自定义 Dataset 加载诗歌文本

3.1 数据来源与格式

本次实战使用chinese_poems.txt作为训练数据集(需放置在data目录下),文件中每行存储一首中文诗歌,需通过自定义Dataset类完成数据加载与预处理。

3.2 自定义 MyDataset 实现(data.py

创建data.py文件,实现继承自torch.utils.data.Dataset的自定义数据集类,完整代码如下:

复制代码
from torch.utils.data import Dataset


class MyDataset(Dataset):
    def __init__(self):
        with open("data/chinese_poems.txt", encoding="utf-8") as f:
            lines = f.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()
    print(len(dataset), dataset[0])

核心解析

  • __init__方法:读取data/chinese_poems.txt文本文件,通过strip()清洗每行数据,去除首尾空格,避免无效字符干扰训练;
  • __len__方法:返回数据集总条数,是Dataset抽象类的必要实现,为数据加载器提供批次计算依据;
  • __getitem__方法:按索引返回单条诗歌文本,支持数据加载器按批次读取数据;
  • 测试代码:主函数中实例化MyDataset,打印数据集长度和第一条数据,便于验证数据加载是否正常。

四、模型加载与数据加载器配置(train.py 核心模块)

4.1 加载 GPT2 预训练模型与 Tokenizer

train.py中,首先导入依赖库并加载中文 GPT2 预训练模型(以 ModelScope 的gpt2-chinese-cluecorpussmall为例)和对应的分词器,核心代码如下:

复制代码
from torch.optim import AdamW
from transformers.optimization import get_scheduler
import torch
from data import MyDataset
from transformers import AutoTokenizer
from transformers import AutoModelForCausalLM, GPT2Model

dataset = MyDataset()
model_dir = r"D:\pyprojecgt\flaskProject\langchainstudy\modelscope\gpt2-chinese-cluecorpussmall";
# 加载编码器
tokenizer = AutoTokenizer.from_pretrained(model_dir)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(model_dir)
print(model)

关键说明

  • AutoTokenizer:自动匹配预训练模型的分词规则,完成中文文本的分词、编码(文本→token ID),是连接自然语言与模型输入的核心组件;
  • AutoModelForCausalLM:加载适用于因果语言建模(文本生成)的 GPT2 模型,内置生成任务的损失计算逻辑,无需手动定义损失函数;
  • GPT2Model:虽导入但未直接使用,是 Transformers 库中 GPT2 基础模型类,可用于自定义模型结构时拓展;
  • print(model):打印模型结构,便于验证模型加载是否完整,排查模型路径错误等问题。

4.2 自定义 collate_fn 实现批量数据处理

批量加载数据时,需统一文本长度(padding)、截断超长文本(truncation),并设置训练标签,collate_fn函数实现如下:

复制代码
def collate_fn(data):
    data = tokenizer.batch_encode_plus(data,
                                       padding=True,
                                       truncation=True,
                                       max_length=512,
                                       return_tensors='pt')
    data['labels'] = data['input_ids'].clone()
    return data

核心原理

  • batch_encode_plus:批量编码文本,padding=True自动补 0 使同批次文本长度一致,truncation=True截断超过 512 长度的文本,return_tensors='pt'返回 PyTorch 张量;
  • 因果语言模型的训练目标是 "根据前文预测下一个 token",因此标签(labels)与输入 ID(input_ids)完全一致 ------ 模型通过输入input_ids[:-1]预测labels[1:],实现自回归训练。

4.3 构建 DataLoader

将自定义Datasetcollate_fn结合,构建数据加载器,实现批量读取数据:

复制代码
# 数据加载器
loader = torch.utils.data.DataLoader(
    dataset=dataset,
    batch_size=4,
    collate_fn=collate_fn,
    shuffle=True,
    drop_last=True,
)
print(len(loader))

参数解析

  • batch_size=4:每次读取 4 条诗歌文本作为一个批次;
  • shuffle=True:训练前打乱数据顺序,避免模型学习到数据的顺序特征;
  • drop_last=True:丢弃最后不完整的批次(若总数据数无法被 batch_size 整除);
  • print(len(loader)):打印加载器的批次总数,便于验证数据加载逻辑。

五、训练流程搭建与核心逻辑解析

5.1 完整训练函数实现

复制代码
# 训练
def train():
    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(loader),
                              optimizer=optimizer)

    model.train()
    for i, data in enumerate(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(), max_norm=1.0)

        optimizer.step()
        scheduler.step()

        optimizer.zero_grad()
        if i % 50 == 0:
            labels = data['labels'][:, 1:]
            out = out['logits'].argmax(dim=2)[:, :-1]

            select = labels != 0
            labels = labels[select]
            out = out[select]
            del select
            accuracy = (labels == out).sum().item() / labels.numel()
            lr = optimizer.state_dict()['param_groups'][0]['lr']
            print(i, loss.item(), lr, accuracy)
    # 保存模型参数,未保存模型结构
    torch.save(model.state_dict(), f='net.pt')
    print("权重保存成功!")

if __name__ == '__main__':
    for epoch in range(1000):
        train()

5.2 核心技术点深度解析

(1)设备选择与模型部署
复制代码
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = model.to(device)

优先使用 GPU(cuda)加速训练,若没有 GPU 则降级使用 CPU,保证代码的跨设备兼容性。

(2)优化器与学习率调度
  • AdamW优化器:相比传统 Adam 优化器,增加了权重衰减(Weight Decay),有效防止模型过拟合,学习率设置为 2e-5(GPT2 微调的经典学习率);
  • 线性学习率调度器:num_warmup_steps=0表示无预热步骤,学习率从 2e-5 开始线性下降,num_training_steps=len(loader)设置总调度步数为数据加载器的批次总数,保证训练过程中学习率平滑下降,提升训练稳定性。
(3)梯度计算与梯度裁剪
复制代码
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
  • loss.backward():反向传播计算梯度;
  • 梯度裁剪:深度 Transformer 模型训练时易出现梯度爆炸问题,通过clip_grad_norm_将梯度的 L2 范数限制在 1.0 以内,避免梯度值过大导致模型训练崩溃。
(4)训练指标监控(每 50 步打印)
复制代码
if i % 50 == 0:
    labels = data['labels'][:, 1:]
    out = out['logits'].argmax(dim=2)[:, :-1]

    select = labels != 0
    labels = labels[select]
    out = out[select]
    del select
    accuracy = (labels == out).sum().item() / labels.numel()
    lr = optimizer.state_dict()['param_groups'][0]['lr']
    print(i, loss.item(), lr, accuracy)

指标计算逻辑

  • labels = data['labels'][:, 1:]:标签取从第 2 个 token 开始的部分(模型需预测下一个 token);
  • out = out['logits'].argmax(dim=2)[:, :-1]:预测值取 logits(原始输出)中概率最大的 token,且截断最后一个 token(与标签长度匹配);
  • select = labels != 0:过滤 padding 填充的 0(无效字符),仅计算有效文本部分的准确率;
  • accuracy:有效 token 的预测准确率,反映模型对诗歌文本的建模效果;
  • 打印内容:训练步数、损失值、当前学习率、预测准确率,便于实时监控训练状态。
(5)模型权重保存与多轮训练
复制代码
torch.save(model.state_dict(), f='net.pt')
print("权重保存成功!")

if __name__ == '__main__':
    for epoch in range(1000):
        train()
  • torch.save(model.state_dict(), 'net.pt'):保存模型权重(仅参数,不包含模型结构),轻量化且便于后续加载;
  • 外层循环for epoch in range(1000):设置训练 1000 个 epoch(轮次),每轮遍历完整的数据集,逐步优化模型参数。

六、运行与结果分析

6.1 运行方式

  1. 确保data/chinese_poems.txt数据集文件存在;
  2. 修改model_dir为本地 GPT2 预训练模型的存放路径;
  3. 直接执行train.py文件,程序自动完成:
    • 加载诗歌数据集并验证;
    • 初始化 GPT2 模型、分词器与训练组件;
    • 循环 1000 轮训练,每 50 步打印训练指标;
    • 每轮训练结束后保存模型权重net.pt

6.2 结果解读

  • 损失值(loss.item ()):随训练步数 / 轮次增加应逐步下降,若损失趋于稳定,说明模型已收敛;若损失上升,需排查学习率过高、梯度爆炸等问题;
  • 学习率(lr):线性调度下逐步下降,训练初期学习率高(快速更新参数),后期学习率低(精细调整参数);
  • 准确率(accuracy):有效文本的 token 预测准确率,准确率越高说明模型对诗歌的语言规律学习越充分;
  • 批次总数(len (loader)):验证数据加载器是否正常,若数值为 0,需检查数据集路径或数据格式。

七、总结与优化方向

7.1 本文总结

本文整合data.pytrain.py完整代码,详细讲解了基于 GPT2 预训练模型的中文诗歌生成训练全流程:

  1. 自定义Dataset加载并清洗诗歌文本,保证数据输入的有效性;
  2. 基于 Transformers 库加载 GPT2 模型与分词器,适配中文文本生成任务;
  3. 实现批量数据处理逻辑,统一文本长度并设置训练标签;
  4. 搭建包含梯度裁剪、学习率调度、指标监控的完整训练流程;
  5. 多轮训练并保存模型权重,为后续诗歌生成推理提供基础。

7.2 优化方向

  1. 增加早停机制:当前固定训练 1000 个 epoch,易导致过拟合,可新增验证集,当验证损失连续多轮未下降时停止训练;
  2. 优化数据加载 :可使用Dataset__getitem__中直接完成单条数据编码,减少collate_fn的计算压力;
  3. 完整模型保存 :替换权重保存方式为model.save_pretrained('gpt2-poetry'),保存完整模型(结构 + 权重),便于后续直接加载推理;
  4. 超参数调优 :尝试调整batch_size(8/16)、学习率(1e-5/5e-5)、max_length(256/1024)等超参数,提升模型生成效果;
  5. 推理功能开发:基于训练后的模型,实现诗歌生成接口,输入 "床前明月光" 等开头文本,自动生成完整诗歌。

八、结语

本文通过完整的代码实现与原理解析,还原了 GPT2 模型在中文诗歌生成任务中的微调训练过程。该框架具备良好的扩展性,只需替换数据集(如小说、对联、文案)并微调超参数,即可适配各类中文文本生成任务。希望本文能为自然语言生成方向的学习者和开发者提供可落地的实战参考。# 基于 GPT2 实现中文诗歌文本生成的训练实战

完整代码

data.py

复制代码
from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self):
        """
        数据集初始化函数
        负责读取数据文件并进行基础的数据清洗
        """
        # 以UTF-8编码打开中文诗歌数据文件
        with open("data/chinese_poems.txt", encoding="utf-8") as f:
            # 读取文件的所有行,每行作为列表中的一个元素
            lines = f.readlines()
        
        # 数据清洗:去除每条数据的前后空格和换行符
        # 使用列表推导式对每一行数据进行strip()处理
        lines = [i.strip() for i in lines]
        
        # 将处理后的数据保存为实例变量,供其他方法使用
        self.lines = lines

    def __len__(self):
        """
        返回数据集的长度
        这是PyTorch Dataset类必须实现的方法之一
        :return: 数据集中样本的总数
        """
        return len(self.lines)

    def __getitem__(self, item):
        """
        根据索引获取单个数据样本
        这是PyTorch Dataset类必须实现的方法之一
        :param item: 数据索引(整数)
        :return: 对应索引的数据样本
        """
        return self.lines[item]


if __name__ == '__main__':
    """
    主程序入口
    当直接运行此脚本时执行以下代码(用于测试数据集类)
    """
    # 创建数据集实例
    dataset = MyDataset()
    
    # 打印数据集大小和第一个样本,用于验证数据加载是否正确
    print(len(dataset), dataset[0])

train.py

复制代码
# 导入必要的库和模块
from torch.optim import AdamW  # AdamW优化器
from transformers.optimization import get_scheduler  # 学习率调度器
import torch  # PyTorch深度学习框架
from data import MyDataset  # 自定义数据集类
from transformers import AutoTokenizer  # 自动分词器
from transformers import AutoModelForCausalLM, GPT2Model  # 预训练模型

# 创建数据集实例
dataset = MyDataset()

# 指定预训练模型的本地路径
model_dir = r"D:\pyprojecgt\flaskProject\langchainstudy\modelscope\gpt2-chinese-cluecorpussmall"

# 加载分词器(用于文本编码和解码)
tokenizer = AutoTokenizer.from_pretrained(model_dir)

# 加载预训练的语言模型(用于因果语言建模,如文本生成)
model = AutoModelForCausalLM.from_pretrained(model_dir)
print(model)  # 打印模型结构,便于调试和了解模型组成

def collate_fn(data):
    """
    数据批处理函数
    将原始文本数据转换为模型可接受的张量格式
    :param 一个批次的文本数据列表
    :return: 编码后的张量字典
    """
    # 使用分词器批量编码文本
    data = tokenizer.batch_encode_plus(data,
                                       padding=True,  # 填充到相同长度
                                       truncation=True,  # 截断超过最大长度的文本
                                       max_length=512,  # 最大序列长度
                                       return_tensors='pt')  # 返回PyTorch张量
    
    # 为语言模型训练创建标签(GPT是自回归模型,输入即标签)
    data['labels'] = data['input_ids'].clone()
    return data

# 创建数据加载器,用于批量加载和预处理数据
loader = torch.utils.data.DataLoader(
    dataset=dataset,  # 使用的数据集
    batch_size=4,  # 每个批次的样本数量
    collate_fn=collate_fn,  # 批处理函数
    shuffle=True,  # 每个epoch打乱数据顺序
    drop_last=True,  # 丢弃最后一个不完整的批次
)
print(len(loader))  # 打印总批次数

# 定义训练函数
def train():
    """
    模型训练函数
    包含完整的前向传播、反向传播和参数更新流程
    """
    global model  # 声明使用全局变量model
    
    # 自动检测并设置计算设备(GPU优先)
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = model.to(device)  # 将模型移动到指定设备
    
    # 创建优化器(AdamW,适合Transformer模型)
    optimizer = AdamW(model.parameters(), lr=2e-5)
    
    # 创建学习率调度器(线性衰减)
    scheduler = get_scheduler(name='linear',
                              num_warmup_steps=0,  # 预热步数为0
                              num_training_steps=len(loader),  # 总训练步数
                              optimizer=optimizer)

    # 设置模型为训练模式(启用dropout等训练专用层)
    model.train()
    
    # 遍历数据加载器中的每个批次
    for i, data in enumerate(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(), max_norm=1.0)

        # 参数更新:使用优化器更新模型参数
        optimizer.step()
        # 更新学习率
        scheduler.step()
        # 清空梯度,为下一批次做准备
        optimizer.zero_grad()
        
        # 每50个批次打印一次训练信息
        if i % 50 == 0:
            # 准备标签和预测结果(忽略序列的第一个token)
            labels = data['labels'][:, 1:]  # 标签,去掉第一个token
            # 获取预测结果(取logits中概率最大的索引),去掉最后一个预测
            out = out['logits'].argmax(dim=2)[:, :-1]

            # 创建掩码,忽略填充token(ID为0的token)
            select = labels != 0
            # 应用掩码,只计算非填充token的准确率
            labels = labels[select]
            out = out[select]
            del select  # 释放掩码张量内存
            
            # 计算准确率:预测正确的token比例
            accuracy = (labels == out).sum().item() / labels.numel()
            
            # 获取当前学习率
            lr = optimizer.state_dict()['param_groups'][0]['lr']
            
            # 打印训练信息:批次索引、损失值、学习率、准确率
            print(i, loss.item(), lr, accuracy)
    
    # 保存模型权重(不包含模型结构,需要原模型结构加载)
    torch.save(model.state_dict(), f='net.pt')
    print("权重保存成功!")

# 主程序入口
if __name__ == '__main__':
    # 进行1000个epoch的训练
    for epoch in range(1000):
        train()  # 调用训练函数

这段代码实现了一个完整的中文GPT-2模型训练流程,包括:

  1. 数据准备:加载自定义数据集和预训练模型

  2. 数据处理:使用分词器将文本转换为模型可接受的张量格式

  3. 训练配置:设置优化器、学习率调度器和训练设备

  4. 训练循环:实现前向传播、损失计算、反向传播和梯度裁剪

  5. 训练监控:定期输出损失、学习率和准确率信息

  6. 模型保存:训练完成后保存模型权重

关键技术点:

  • 使用Hugging Face Transformers库简化模型加载和训练

  • 实现梯度裁剪防止训练不稳定

  • 使用学习率调度器优化收敛过程

  • 准确计算忽略填充token的预测准确率

  • 支持GPU加速训练

相关推荐
zhangfeng11332 小时前
大模型微调主要框架 Firefly vs LLaMA Factory 全方位对比表
人工智能·语言模型·开源·llama
爱打代码的小林2 小时前
OpenCV 实现实时人脸检测
人工智能·opencv·计算机视觉
cyyt2 小时前
深度学习周报(1.26~2.1)
人工智能·深度学习
YOLO视觉与编程2 小时前
yolo26目标检测可视化界面系统源码
人工智能·目标检测·计算机视觉
你大爷的,这都没注册了2 小时前
配置阿里百炼云平台的api_key
人工智能
Faker66363aaa2 小时前
青香蕉尺寸分类与检测:从小尺寸香蕉手识别到模型优化_cascade-rcnn_hrnetv2p-w40-20e_coco
人工智能·分类·数据挖掘
shangjian0072 小时前
AI-大语言模型LLM-模型微调8-进阶操作
人工智能·深度学习·语言模型
xindoo2 小时前
我用AI写了部小说,这里是整个过程
人工智能
传说故事2 小时前
【论文自动阅读】快速视频生成的过渡匹配蒸馏
人工智能·视频生成