30u30 中文导读 / 30 天目录 / 第 1 天

第 1 天 · 只学预测下一个字符,RNN 就能写出莎士比亚

第 1 单元The Unreasonable Effectiveness of Recurrent Neural NetworksAndrej Karpathy博客 · 2015
这是导读,不是翻译。先用 10 分钟拿到这篇论文的思想地图,再去读 原论文 →,去原仓库的 01_Unreasonable_Effectiveness/ 目录 → 动手写代码。
给孩子讲:这一天有 10 岁孩子能听懂的讲义,带猜字游戏和动手活动,可打印。打开儿童讲义 → · PDF 简体 · PDF 繁體

一句话

Karpathy 训练了一个只做“预测下一个字符”的循环神经网络。它在莎士比亚、维基百科、LaTeX 和 Linux 源码上都生成了格式像样的文本。他还打开模型内部,找到了追踪引号和 URL 的神经元。这篇博文让大众第一次直观看到:简单目标加足够数据,模型就能自己学到结构。

论文讲了什么

这篇博文回答一个问题:只给模型一个目标——预测下一个字符——它能学到多少结构?Karpathy 用 6 组实验给出答案:比多数人预想的多。

模型。前馈网络的输入和输出长度固定。循环神经网络(Recurrent Neural Network, RNN)每一步读入一个字符,更新隐藏状态(hidden state),再输出下一个字符的概率分布。核心更新只有一行:h_t = tanh(W_hh · h_{t-1} + W_xh · x_t),输出为 y_t = W_hy · h_t。博文的实验实际使用多层 LSTM,第 2 天会拆开讲它。

训练。字符用独热编码(one-hot)输入。损失是每一步预测的交叉熵。梯度用时间反向传播(Backpropagation Through Time, BPTT)计算。

生成。先给模型一个起始字符。从输出分布中采样一个字符,把它喂回输入,然后重复。采样前把 logits 除以温度(temperature)。温度低时,文本保守且容易重复。温度高时,文本多样但错误多。

6 组数据,同一套代码:

打开黑盒。Karpathy 逐个查看隐藏单元的激活值。一个单元在 URL 内部激活,在 URL 外部关闭。一个单元只在维基链接的双方括号内激活。还有一个单元在引号内外切换。这些功能都没有人手工设计,全部来自“预测下一个字符”这一个目标。

局限。生成文本局部通顺,长程连贯性差。段落之间没有主线。代码常引用从未定义的变量。博文结尾称注意力机制(attention)是神经网络近来最有意思的结构创新。

关键概念

循环神经网络(Recurrent Neural Network, RNN)
每个时间步共享同一组权重,用隐藏状态把读过的内容带到下一步。它能处理任意长度的序列,参数量不随序列长度增长。
字符级语言模型(character-level language model)
以单个字符为单位,输出下一个字符的概率分布。词表只有几十到几百个符号,不需要分词;代价是序列更长,学习长程结构更难。
时间反向传播(Backpropagation Through Time, BPTT)
把 RNN 沿时间展开成一个很深的前馈网络,再做普通反向传播。实践中只展开固定步数,例如 25 步,这叫截断 BPTT。
温度采样(temperature sampling)
采样前把 logits 除以温度 T,再做 softmax。T 小于 1 时分布更尖,T 大于 1 时分布更平。大语言模型 API 里的 temperature 参数就是它。
隐藏状态(hidden state)
一个固定长度的向量,是 RNN 对“到目前为止读过什么”的压缩记忆。所有长程信息都必须挤进这个向量,它的维度决定记忆容量。

历史位置:为什么值得读

2015 年之前,RNN 已有 20 多年历史。但多数工程师认为它难训练、不实用。这篇博文同时开源了 Lua Torch 版的 char-rnn 和一个约 100 行的 NumPy 版 min-char-rnn。任何人都能在一台带 GPU 的电脑上复现结果。社区随后用它生成了菜谱、乐谱和各种风格的文本。

它的价值不在新算法。LSTM 早已存在,Graves 2013 年也用 LSTM 做过字符级文本生成。它的价值在示范:同一个目标、同一份代码,换一份数据就学到一种新结构。这正是后来 GPT 系列的核心思路:用下一个词元预测(next-token prediction)做通用预训练。

和清单其他天的关系:

学前准备

在原仓库怎么学

原仓库第 1 天用纯 NumPy 实现字符级 RNN。建议按下面的顺序学:

  1. 先读 Karpathy 原博文,再打开 README.md 看本天的目录结构。
  2. 读 implementation.py。CharRNN 类基于 min-char-rnn,依次看 forward、loss、backward、sample 4 个方法。
  3. 运行 train_minimal.py,用 data/tiny_shakespeare.txt 训练。每隔一段时间看一次生成样本的变化。
  4. 打开 notebook.ipynb 交互式跑一遍。用 visualization.py 画损失曲线、隐藏状态演化和字符概率热图。
  5. 做 exercises/ 里的 5 个练习:从零搭 RNN、温度采样、换自己的数据集、损失可视化、莎士比亚与海明威风格分类器。
  6. 卡住时再看 exercises/solutions/。最后用 CHEATSHEET.md 复习。

建议:手写 BPTT 时,对照 backward 方法逐行核对梯度公式。改完代码后,先在几百个字符的小文本上训练,确认模型能把损失降到接近 0,再换回完整数据。这一步能快速排除实现错误。

今天再看

还活着的:“预测下一个词元”仍是所有大语言模型的预训练目标。温度采样仍是生成时最常调的参数。Karpathy 找“会追踪 URL 的神经元”的做法,发展成了今天的机制可解释性(mechanistic interpretability)研究:在大模型里找特征和回路。

被取代的:字符级输入被子词分词(subword tokenization,例如 BPE)取代,序列更短,效率更高。RNN 主干被 Transformer 取代。原因是 Transformer 训练时能并行处理整段序列,RNN 必须一步一步算。

回潮的:2023 年起,Mamba 这类状态空间模型和 RWKV 把“固定大小状态 + 线性时间推理”带了回来。它们可以看作能并行训练的新型 RNN。Karpathy 后来的 nanoGPT 教程,第一个示例数据集仍是字符级的莎士比亚。

容易卡住的地方

延伸阅读