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
但模型只认识数字,所以需要:
- 字符表:构造 stoi = {“h”:0, “e”:1, …, ” “:5}
- 字符编码:把文本转成 [0, 1, 2, …]
- 滑动窗口:切成小段做训练(输入/标签)
这些逻辑统一封装成一个 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,可以用来:
- 加载数据集(dataset.py)
- 初始化模型(model.py)
- 用 CrossEntropyLoss 和 Adam 优化器训练
- 每隔几步打印 loss、保存模型
- 训练完成后生成样例文本
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。