page contents

Python 微型 GPT 实现:从零开始训练字符生成模型

MicroGPT 只使用 32 k 条人名文本(每行一个姓名)作为训练语料。词表由 26 个小写字母加一个 BOS(Beginning‑of‑Sequence)标记组成,大小仅为 27。这种极简词表在 字符级语言模型 中易于实现,却限制了模型的 表达能力 与 泛化范围:

attachments-2026-03-diB0QtK669a4ef12c9cf6.pngMicroGPT 只使用 32 k 条人名文本(每行一个姓名)作为训练语料。词表由 26 个小写字母加一个 BOS(Beginning‑of‑Sequence)标记组成,大小仅为 27。这种极简词表在 字符级语言模型 中易于实现,却限制了模型的 表达能力 与 泛化范围:  

只能捕获字母顺序的局部统计,无法学习更高层次的语义结构。  

对长度分布的适应性受限,生成的名字往往在真实姓名长度区间内波动。  

由于数据规模极小,模型容易 过拟合,在未见姓名上表现不佳。

这些限制正是学习 LLM 基础原理的好切入点——可以在可控环境下观察每一步计算的影响。

字符→整数的最简 Tokenizer 实现

# 仅含 27 个 token 的映射表

stoi = {ch: i for i, ch in enumerate("abcdefghijklmnopqrstuvwxyz")}

stoi["<BOS>"] = 26

itos = {i: ch for ch, i in stoi.items()}

def encode(s: str) -> list[int]:

    return [stoi["<BOS>"]] + [stoi[c] for c in s] + [stoi["<BOS>"]]

def decode(ids: list[int]) -> str:

    return "".join(itos[i] for i in ids if i != 26)

stoi / itos

:字符‑整数 双向映射。  

BOS

:在序列首尾加入,同步指示 序列起止,帮助模型学习首字符与终止概率。

滑动窗口生成训练样本的序列对齐逻辑

MicroGPT 采用 左侧上下文 → 右侧目标 的滑动窗口。对编码后的序列 ids,长度为 L,生成 (L‑1) 对 (x, y):


xs, ys = [], []

for i in range(len(ids) - 1):

    xs.append(ids[: i + 1])   # 包含 BOS 与已见字符

    ys.append(ids[i + 1])      # 下一个字符作为目标

上下文增长

:模型在每一步看到的输入比前一步多一个 token。  

目标移动

:对应的 标签 永远是序列中紧随上下文的下一个 token。

数值 Logits 通过 Softmax 转概率的数值稳定技巧

def softmax(logits: list[float]) -> list[float]:

    max_logit = max(logits)                 # 防止 exp 溢出

    exp_vals = [math.exp(l - max_logit) for l in logits]

    sum_exp = sum(exp_vals)

    return [v / sum_exp for v in exp_vals]

**减去 max_logit**:不影响结果,但避免 exp(>100) 产生 inf。  

指数放大

:即使相邻 logits 差距很小,softmax 也会产生明显的 概率峰值,强化模型的 确定性预测。

交叉熵损失计算与梯度回传的核心公式

loss = -log(p_target)

其中 p_target 为 softmax 输出中对应正确 token 的概率。  


当 p_target = 0.9 时,loss ≈ 0.105(模型信心高)。  

当 p_target = 0.01 时,loss ≈ 4.605(惩罚强)。

梯度通过 链式法则 逐层传播:

# 对 logits 的梯度

dlogits = probs

dlogits[target] -= 1  # ∂loss/∂logits

# 再向前传播到嵌入矩阵、线性层等

此过程在 每个参数 上累计 微小梯度,驱动参数朝降低 交叉熵 的方向更新。

全参数梯度累积的手工反向传播实现

MicroGPT 在纯 Python 环境中手动实现 反向传播,不依赖自动微分框架。关键步骤:

前向保存中间值

:embeddings, hidden, logits, probs。  

从 loss 开始计算梯度

:如上所示的 dlogits。  

线性层梯度

:dw = hidden.T @ dlogits、db = dlogits.sum(0)。  

嵌入层梯度

:demb = dlogits @ w.T,再通过 索引累加 回到对应字符的 embedding 向量。

虽然效率低下,却让读者 逐行观察 每一次数值如何影响参数更新。

无库实现的训练速度与 GPU 加速缺失的代价

CPU 纯 Python

:循环与列表操作导致 每步毫秒级,在 32 k 示例上约需 数分钟 完成一次 epoch。  

缺少向量化

:未使用 numpy/torch 的 SIMD 优化,导致 内存带宽 与 缓存利用率 均不佳。  

无法利用 GPU

:GPU 上的矩阵乘法可以将同等计算量提升 数十倍,但手工实现难以兼容。

因此,MicroGPT 适合作为 教学原型,而非大规模训练基准。

部署微型 GPT 时的数据预处理与超参数调优要点

固定词表

:在生产环境保持 字符 → id 映射不变,防止未知 token 引入错误。  

BOS 包装

:每次推理时显式在输入前后添加 BOS,确保模型能预测序列起止。  

学习率衰减

:使用 余弦退火 或 StepLR,在前几百步保持较高学习率,随后逐步下降以防过拟合。  

批大小

:对极小数据集,batch=32 已足够;更大 batch 只会增加显存占用而不提升收敛速度。

遵循以上要点,可在 资源受限 的环境(如边缘设备)快速部署可交互的字符生成模型。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1823 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2228 文章
  3. Pack 1823 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章