AK大神情人节前(2.12)写了篇博客:
https://karpathy.github.io/2026/02/12/microgpt/
分享如何用200行Python代码从零写一个类ChatGPT算法,用于生成人名。
项目叫MicroGPT,已开源到Github:
https://gist.github.com/karpathy/8627fe009c40f57531cb18360106ce95
最近有好心人用更通俗的语言和图表写了篇解读:
https://growingswe.com/blog/microgpt
发现还是有点难懂,让AI重写一遍,去公式,加名词解释。
现在终于能看懂部分了。
训练数据:32000个名字
模型要学什么?
32000个人名,一行一个:emma、olivia、ava、isabella、sophia...
任务很简单:学会这些名字的统计规律,然后生成新的、听起来像真名字的词。
训练完之后,模型能输出"kamon"、"karai"、"anna"、"anton"这样的名字。
它学会了哪些字母容易跟在哪些后面,哪些音节常出现在开头或结尾,一个名字通常多长。
从ChatGPT的视角看,你跟它的对话就是一个文档。
你输入提示词,它的回应就是统计意义上的文档续写。
第一步:把文字变成数字
神经网络只认数字,不认字母。
所以得有个转换方式,最简单的做法是这样的:
给每个独特字符分配一个整数。
26个小写字母得到0到25的编号,再加一个特殊标记BOS(Beginning of Sequence,序列开始),编号26,用来标记名字的起止。
Tokenizer(分词器):把文本转换成数字序列的工具。就像给每个字符发一张身份证,上面写着专属编号。
比如"emma"变成:
BOS(26) → e(4) → m(12) → m(12) → a(0) → BOS(26)
这些整数本身没有大小关系。
4不比2"更大",它们只是不同的符号,就像给每个字母涂上不同颜色。
生产环境的tokenizer(比如GPT-4用的tiktoken)会按字符块切分,词汇表有10万个token,但原理一样。
预测游戏:猜下一个字
核心任务来了:给定目前看到的token,预测下一个是什么。
我们一个位置一个位置往前滑。
在位置0,模型只看到BOS,要预测第一个字母。
在位置1,它看到BOS和第一个字母,要预测第二个字母。以此类推。
对于"emma"这个名字,会产生5个训练样本:
• 看到[BOS] → 预测"e"
• 看到[BOS, e] → 预测"m"
• 看到[BOS, e, m] → 预测"m"
• 看到[BOS, e, m, m] → 预测"a"
• 看到[BOS, e, m, m, a] → 预测BOS(结束)
这个滑动窗口,就是所有语言模型的训练方式,ChatGPT也一样。
从分数到概率:让模型给出确定答案
每个位置,模型输出27个原始分数,对应27个可能的下一个token。
这些分数可以是任何值:正的、负的、大的、小的。
Logits(原始分数):模型最初输出的未经处理的数字,就像考试的原始卷面分,还没有转换成等级。
我们需要把它们转成概率:正数,加起来等于1,Softmax就干这个事。
你可以这样理解:假设模型给5个候选字母打分是[1.2, 2.8, 0.5, 1.8, -0.3]。
Softmax做两件事:
1. 把每个分数变成正数(通过指数运算)
2. 让它们加起来等于100%
结果就是一个概率分布,比如"e"有60%的可能,"a"有22%的可能。
Softmax:把任意数字转换成概率分布的函数。就像把考试分数转换成排名百分比。
注意一个大的分数会主导整个分布,因为指数运算会放大差异。
这也是为什么模型能"确信"某个答案。
代码里有个小技巧:先减去最大值再做指数。
数学上结果一样,但能防止计算溢出。
衡量错误:模型猜得有多离谱
预测有多离谱?
我们需要一个数字来表示"模型觉得正确答案有多不可能"。
交叉熵损失(Cross-Entropy Loss):衡量预测和真实答案之间差距的指标。模型越确信错误答案,惩罚越重。
举个例子:
• 如果模型给正确答案90%的概率,损失很小(0.1)
• 如果只给1%的概率,损失很大(4.6)
• 如果完全确信正确答案(100%),损失是0
模型不仅要答对,还要"有信心地答对"。
如果它给正确答案只分配了很小的概率,即使蒙对了,损失也会很大。
训练就是让这个数字不断变小。
反向传播:找到问题出在哪
模型有4192个参数(可以理解为4192个旋钮)。
要改进,需要知道:每个旋钮往上调一点点,损失会上升还是下降?
反向传播(Backpropagation):倒着追踪计算过程,找出每个参数对最终错误的贡献。就像出了事故,倒推每个环节的责任。
想象一下多米诺骨牌。
前向计算是推倒第一张牌,一路传导到最后。
反向传播是从最后一张倒推回去,看每张牌对最终结果的影响有多大。
每个数学操作(加、乘、指数、对数)记住自己的输入。
反向传播从损失开始,沿着计算路径往回走,给每个参数算出一个"责任分数"。
梯度(Gradient):告诉你参数该往哪个方向调整,以及调整的力度。就像指南针,指向"让错误变小"的方向。
举个例子,假设参数a在两个地方被用到。
那它的总责任是两条路径的责任之和。
这就是为什么有些参数的梯度特别大,它们在多个地方影响了最终结果。
PyTorch的loss.backward()跑的就是这个算法,只不过操作的是大批量数据而不是单个数字。
从ID到意义:给每个字符一个性格
原始token ID(比如4)只是个索引,模型没法直接用整数做数学运算。
所以每个token会对应一个16个数字的列表。
可以理解为每个token有个16维的"性格档案",训练时可以调整。
嵌入(Embedding):把离散的符号(比如字符)转换成连续的数字向量。就像给每个人建立一份多维度的性格档案。
位置也重要,位置0的"a"和位置4的"a"作用不同,所以有第二份档案,按位置索引。
两份档案相加,形成输入到网络的向量。
这些数字一开始是随机的小数,训练过程中,模型会自己调整。
训练后,行为相似的token(比如元音字母)往往有相似的向量。
模型从零学习这些表示,不需要预先知道什么是元音。
就像小孩学说话,没人告诉他元音和辅音的区别,但他自己能总结出规律。
Token之间怎么交流:注意力机制
这是transformer的核心。
每个位置需要从之前的位置收集信息。
想象你在读一个句子,读到"她"的时候,你会回头看看前面提到的女性角色是谁,这就是注意力在做的事。
注意力机制(Attention):让模型决定在处理当前位置时,应该重点关注之前哪些位置的信息。就像你读书时,视线会在不同词语之间跳转。
每个token产生三个东西:
• Query("我在找什么?")
• Key("我包含什么?")
• Value("如果被选中,我提供什么信息?")
你可以这样理解:Query是搜索关键词,Key是每个位置的标签,Value是实际内容。
模型用Query去匹配所有之前位置的Key,找到最相关的,然后把对应的Value拿过来。
有个重要限制:每个位置只能看过去,不能看未来。
位置2不能关注位置4,因为位置4还没发生。
这让模型成为自回归的,符合我们生成文本的方式。
多头注意力(Multi-Head Attention):同时运行多个注意力机制,每个关注不同的模式。就像同时用多个视角看同一个问题。
不同的注意力头学习不同模式:
• 一个头可能强烈关注最近的token
• 另一个可能聚焦开头的BOS token(记住"我们在生成名字")
• 第三个可能寻找元音
四个头并行工作,最后把结果拼起来。
完整流程:信息怎么在模型里流动
把每个token想象成一个包裹,在流水线上经过多个工序:
1. 嵌入:给包裹贴上标签(token性格 + 位置信息)
2. 归一化:把包裹整理成标准大小
3. 注意力:包裹之间互相交换信息
4. 加残差:保留原始包裹的一部分
5. 归一化:再次整理
6. MLP:每个包裹独立加工
7. 加残差:再次保留原始信息
8. 输出:得到27个分数,对应下一个可能的字符
MLP(多层感知机):一个简单的神经网络,负责对每个位置的信息做独立处理。如果说注意力是"交流",MLP就是"独立思考"。
残差连接(Residual Connection):在处理信息时保留原始输入的一部分。就像做菜时保留一些原材料的原味,不要全部加工掉。
残差连接特别关键,想象信息在网络里传递就像打电话,每传一层就会损失一些。
残差连接相当于给信息开了一条高速通道,直接跳过某些层,保证重要信息不会丢失。
没有它,深度网络根本训练不起来。
RMSNorm(均方根归一化):把每个向量缩放到标准大小。就像把不同大小的照片统一调整成同样尺寸,方便处理。
学习过程:模型怎么变聪明
训练循环重复1000次,每次做这些事:
1. 随机挑一个名字
2. 把它变成数字序列
3. 在每个位置运行模型,预测下一个字符
4. 计算预测有多离谱(损失)
5. 反向传播,找出每个参数的责任
6. 调整参数,让下次预测更准
Adam优化器:一个聪明的参数更新策略。它会记住每个参数最近的表现,对稳定的参数走大步,对摇摆不定的参数走小步。
损失从约3.3开始(完全随机猜,27个选项中瞎蒙),慢慢降到2.37左右。
生成的名字也从乱码演变成合理的:
• 刚开始:xqbzjfmwplk(完全乱码)
• 训练中期:kamon, karai(有点意思了)
• 训练后期:anna, anton(完全像真名字)
你能看到模型在"开窍"。
它逐渐理解了什么是合理的字母组合,什么样的音节听起来像名字。
生成新名字:让模型自由发挥
训练完成后,生成很直接:
1. 从BOS开始
2. 让模型预测下一个字符
3. 得到27个概率
4. 随机选一个(按概率)
5. 把选中的字符喂回去
6. 重复,直到模型说"我完成了"(输出BOS)
温度(Temperature):控制生成的随机性。低温度让模型更保守(总选最可能的),高温度让模型更大胆(更多尝试不常见的选择)。
你可以这样理解温度:
温度0.5:模型很谨慎,倾向于生成常见的、安全的名字
温度1.0:模型按它学到的真实概率生成
温度2.0:模型很大胆,会尝试不寻常的组合
温度太低,生成的东西很无聊,总是那几个最常见的名字。
温度太高,生成的东西可能是胡话。
对于名字,最佳点在0.5左右。
既有创意,又不会太离谱。
其他都是效率问题
这个200行脚本包含完整算法。
从这个算法到ChatGPT的完整实现,核心思想没变。
变的是什么?规模和效率:
• 训练数据:从32000个名字到整个互联网的文本
• 词汇表:从27个字符到10万个子词
• 参数:从4192个到数千亿个
• 层数:从1层到几百层
• 硬件:从你的笔记本到数千个GPU集群
• 训练时间:从几分钟到几个月
但循环是一样的:
分词 → 嵌入 → 注意力 → 计算 → 预测下一个token → 衡量错误 → 反向传播 → 调整参数 → 重复
就这么简单,也就这么复杂。
你现在知道了,ChatGPT并不神秘。
它就是一个超大规模的"猜下一个词"游戏,玩了无数次之后,变得出奇地聪明。

