从零训练 miniGPT:从一句话到《飞鸟集》的生成实验

1.项目目标与语言建模简介

    我们想做一个语言模型,比如:

    • 给一句话开头:“你好,今天天气”
    • 让模型自动生成后面:“真不错呀!”

    这就叫做语言建模(Language Modeling),其核心任务是:

    给定一个 token 序列,预测下一个 token。

    2. 项目模块划分与职责

    构建这样一个系统,需要四大模块:

    模块问题对应文件
    1. 数据准备我训练用什么?怎么变成模型可以学的格式?dataset.py + data/tiny.txt
    2. 模型结构我怎么构造一个神经网络来预测下一个 token?model.py
    3. 模型训练我怎么训练它?损失函数、优化器怎么选?train.py
    4. 模型推理训练好了,怎么用它生成文本?generate.py

    3. 数据准备:dataset.py

    神经网络无法直接处理字符文本,必须将字符串转为张量(数字)。

    我们写的文本是字符串,比如:

    text

    复制粘贴

    hello world

    但模型只认识数字,所以需要:

    1. 字符表:构造 stoi = {“h”:0, “e”:1, …, ” “:5}
    2. 字符编码:把文本转成 [0, 1, 2, …]
    3. 滑动窗口:切成小段做训练(输入/标签)

    这些逻辑统一封装成一个 CharDataset 类,让模型可以通过python复制编辑数据。

    数据的读取、清洗和分批交给 dataset.py。

    4. 模型结构:model.py

    将模型结构独立成文件。模型是独立组件,训练和使用时都可能被复用。

    这样可以

    • 训练一个模型
    • 然后另一个脚本加载它来生成文本
    • 或者以后换一种训练方式,但模型结构不变

    所以把模型定义写在 model.py 里,让训练脚本和推理脚本都能导入它,这样有利于 模块化、复用 和 解耦。

    5. 模型训练:train.py

    用专门的训练脚本,结构清晰、可复用、更好调参。

    负责:

    • 加载数据
    • 初始化模型
    • 设置优化器
    • 多轮训练模型
    • 保存权重

    优点:

    • 改训练数据不改模型
    • 改超参数不动模型结构
    • 单独训练 / 单独生成 / 单独评估

    6.模型存放目录和格式

    checkpoints/里存放训练好的模型,格式是 .pt 文件

    7. 文本生成:generate.py

    generate.py可以加载训练好的模型进行推理。

    模块 职责
    dataset.py 数据准备,负责“吃数据”
    model.py 网络结构,负责“算预测”
    train.py 训练过程,负责“学知识”
    generate() 推理脚本,负责“写句子”
    checkpoints/ 保存模型,方便下次用

    8.具体的实现过程

    因此这样创建我们的目录结构。

    我们先在 data/tiny.txt里加入一句

    “你好,世界!今天天气很好,我们出去玩吧。”

    这就是我们要训练的“语言语料”。

    接下来我们开始写 dataset.py,让模型能够:

    • 读取文本
    • 构建字符到索引的映射
    • 切分成训练对
    功能描述
    CharDataset一个字符级的数据集类
    stoi / itos字符和数字的相互转换字典
    __getitem__()把连续文本切成输入-目标对:比如输入 “你好今”,目标 “好今日”
    load_dataset()从 txt 文件中读取文本,返回可用于训练的数据集对象

    然后我们开始实现model.py,搭建最基础的 GPT 模型结构(MiniGPT)。

    主要包括:

    • Token Embedding
    • Position Embedding
    • 一个简化版 Transformer block
    • 输出预测下一个 token 的概率

    我们已经完成了 model.py,构建了一个最小化但功能完整的 GPT 模型。

    分模块解释它的构成:

    1.SelfAttention

    实现一个单头注意力机制(Multi-Head 的简化版本)

    • 用三个 Linear 层分别计算 Query、Key、Value
    • 使用点积注意力 Q·Kᵀ 并除以缩放因子
    • 使用 tril 掩码防止模型看“未来”
    • 输出加权后的 V

    2. TransformerBlock

    类似 GPT 的结构:LayerNorm → SelfAttention → LayerNorm → FeedForward

    • 每层都有残差连接(x + …)
    • 保持层的可稳定训练
    • 简化版本,易于理解

    3.MiniGPT

    这是整个模型的封装,它包含:

    组件作用
    token_embedding把字符索引转成向量
    position_embedding加入位置信息
    blocks一堆 Transformer Block
    ln_f最终归一化层
    head输出到 vocab 的 logits,预测下一个字符的分布

    generate() 方法

    这是我们自定义的推理函数,可以:

    • 接收一个种子序列(字符索引)
    • 每次预测下一个 token 并采样加入序列
    • 最后解码成字符串返回

    接下来我们来写训练脚本 train.py,可以用来:

    1. 加载数据集(dataset.py)
    2. 初始化模型(model.py)
    3. 用 CrossEntropyLoss 和 Adam 优化器训练
    4. 每隔几步打印 loss、保存模型
    5. 训练完成后生成样例文本

    9.训练结果

    运行后可以看到输出结果,模型已经训练成功了,loss 也下降得很好,说明它几乎记住了这句话。这符合 小数据+小模型的过拟合特征:它学会了死记硬背,但没有泛化能力,因为我们只喂了一句话。

    当前结果说明:

    • 训练正确 :loss 降得非常低,说明模型可以精确预测每个字符。
    • 生成逻辑正常 :generate 函数成功按字符拼接生成了新文本。
    • 记忆能力优秀 :模型把“我们出去玩吧”重复生成,说明它记住了结构

    现在checkpoints下面已经生成了名为mini-gpt.pt的模型

    我们也可以用generate.py来加载模型并输出结果。

    10.可视化训练过程并扩展模型

    接下来我们增加“训练 loss 曲线可视化”功能 和 尝试模型多层化,都是对miniGPT 项目的扩展,逐步升级为一个更完整、更强大、更可分析的 Transformer 训练系统。

    训练 loss 曲线可视化(理解模型有没有“学会”)

    可以帮助监控模型学习过程,判断:

    • 模型是否在正常下降 loss;
    • 有没有过拟合/欠拟合;
    • 参数(如学习率)选得合不合理;
    • 模型是否陷入震荡、不收敛。

    只需要在 train.py 中记录每个 eval_interval 的 loss,并用 matplotlib 绘图:

    我们在train.py中加入绘制过程,并初始化模型的Transformer 层数为4层。

    就可以看到对应的图表了。

    11. 模型扩展与优化建议

    接下来我们可以自行尝试:

    1. 加入更多训练文本(提高泛化能力)

    你可以将 tiny.txt 替换为多句子文本,例如:

    你好,世界!今天天气很好,我们出去玩吧。

    你好,我是一个智能体。

    早上好,今天是星期五。

    我们去公园玩耍吧。

    这样模型就不会只重复一句话。

    2. 尝试生成不同开头

    你可以换成其他起始 token,比如:

    context = torch.tensor([[stoi[‘今’]]], dtype=torch.long).to(device)

    print(model.generate(context, max_new_tokens=100, stoi=stoi, itos=itos))

    3. 更大模型结构

    model = MiniGPT(vocab_size=vocab_size, block_size=block_size, embed_size=128, num_layers=4).to(device)

    但对显存和训练时间要求会提高。

    12.将语料替换为飞鸟集

    接下来我尝试把tiny.txt换成了飞鸟集的中文文本。

    Loss 值范围含义
    0.0 ~ 0.1非常好,模型基本拟合成功
    0.1 ~ 1.0学习中,正在收敛
    >1.0模型预测差(初期常见)或过拟合/欠拟合
    极大或NaN模型崩溃了,可能梯度爆炸、学习率太大等问题

    如果模型预测的词概率分布和 targets 一致,loss 就会趋近于 0。

    如果模型乱猜,loss 会比较高。

    大参数的loss值明显高于小参数。小参数的训练结果反而明显好于大参数。

    我们使用的语料(飞鸟集)本身比较小、风格一致、词汇量有限。用过大的大模型(比如:

    • embedding 维度高(256、512)
    • 层数多(6 层 transformer)
    • attention head 多

    会导致模型难以训练好,甚至严重过拟合或陷入局部最优。小模型在小语料上反而更容易学到模式。

    9. 训练结果与总结

    我们的模型已成功记忆训练语料,并能生成流畅文本

    小模型更适合小语料,避免过拟合与梯度不稳定

    可视化训练曲线有助于判断模型是否收敛

    下一步计划:系统性测试模型规模对训练影响

    文中涉及到的所有工程文件!!!!!

    https://github.com/spikec137/miniGPT

    参考连接:

    https://www.shigeku.org/shiku/ws/wg/tagore.htm

    发布于2025/08/03。

    船长

    发表评论