MLAlgorithms RNN与LSTM深度解析:二进制加法与文本生成两大经典实现
【免费下载链接】MLAlgorithmsMinimal and clean examples of machine learning algorithms implementations项目地址: https://gitcode.com/gh_mirrors/ml/MLAlgorithms
MLAlgorithms 是一个用极简、干净的 Python 代码从零实现机器学习算法的开源项目,其中 mla/neuralnet/layers/recurrent/ 下的 RNN 与 LSTM 层,配合 examples/nnet_rnn_binary_add.py 的二进制加法和 examples/nnet_rnn_text_generation.py 的文本生成两个示例,是理解循环神经网络内部机制的绝佳起点。本文带你读懂这两个经典实现的核心思想与代码结构。
为什么选择这个项目学习 RNN?
大多数深度学习框架(如 Keras、PyTorch)封装得很好用,但"黑盒"感也强。如果你想知道:
- 隐藏状态
h到底是怎么一步步更新的? - 梯度是怎么"沿时间反向传播"(BPTT)的?
- LSTM 的门控公式在代码里长什么样?
MLAlgorithms 的答案就是:不依赖任何框架,只用 numpy + autograd 手写全部核心逻辑,代码量小到可以逐行读懂。RNN 相关代码集中在以下位置:
| 文件 | 作用 |
|---|---|
| mla/neuralnet/layers/recurrent/rnn.py | 原生 RNN 层(前向 + BPTT 反向传播) |
| mla/neuralnet/layers/recurrent/lstm.py | LSTM 层(四个门控的完整实现) |
| mla/neuralnet/nnet.py | 通用神经网络容器,串联各层完成训练 |
RNN 核心原理:一个状态矩阵记住过去
原生 RNN 的精髓可以用一句话概括:每个时间步的输出,都依赖上一个时间步的隐藏状态。
在 rnn.py 的forward_pass(L55-L74)中,核心循环极其精炼:
for i in range(n_timesteps): states[:, i, :] = np.tanh( np.dot(X[:, i, :], p["W"]) # 当前输入的影响 + np.dot(states[:, i - 1, :], p["U"]) # 上一步隐藏状态的影响 + p["b"] )其中W是"输入→隐藏"权重,U是"隐藏→隐藏"权重,tanh把状态压缩到 (-1, 1)。整段序列的隐藏状态被存进一个(样本数, 时间步数, 隐藏维度)的张量states。
反向传播在backward_pass(L76-L104)中完成:沿时间轴倒序遍历每个时间步,把当前步的误差delta和来自未来的误差dh_next合并后回传,这就是经典的 BPTT。初学者可以对照这两个函数,亲手推一遍每个矩阵的维度变化——这是理解 RNN 最快的方式。
⚠️ 原生 RNN 的已知短板:梯度沿时间反向传播时容易消失或爆炸,难以学习长距离依赖。这正是 LSTM 登场的理由。
LSTM 核心原理:用"门"精细控制记忆流动
LSTM 引入了**细胞状态(cell)**这条"记忆高速公路",并用四个门来控制信息的写入、遗忘与读出。lstm.py 在setup(L47-L85)中清晰地定义了参数命名约定:
i(input gate,输入门)f(forget gate,遗忘门)o(output gate,输出门)c(cell,细胞状态)
每个门各有一套W(输入权重)、U(隐藏权重)和b(偏置),共 12 个参数矩阵。
前向过程(L87-L131)的每一步只做了三件关键的事:
- 算门:三个门过
sigmoid(输出 0~1 的"开关程度"),细胞候选值过tanh; - 更新记忆:
新状态 = 旧状态 × 遗忘门 + 输入门 × 细胞候选值(L117-L120)——这就是"有选择地记住或遗忘"; - 输出:
输出 = 输出门 × tanh(新状态)。
正因为存在这条可以长期保持数值的细胞状态,LSTM 能学到 RNN 学不到的长距离依赖。反向传播(L133-L189)则分别对四个门求导,结构上比 RNN 多了一层"门控导数",但思路完全一致:倒序遍历时间步,累积各权重矩阵的梯度。
实战一:二进制加法——让网络"学会算术"
这是循环网络最迷人的演示之一:不教网络任何数学知识,只给它成对的二进位数,它就能自己"悟出"进位规则。
示例代码在 nnet_rnn_binary_add.py:
- 数据生成(L20-L53):随机取两个 8 位以内的数,转成二进制串并低位在前排好(加法从最低位开始,天然适合逐位顺序处理),形状为
(样本数, 8, 2); - 模型结构(L56-L72):只有三层——
LSTM(16)→TimeDistributedDense(1)→sigmoid,损失函数用均方误差mse,优化器用 Adam; - 评估技巧(L69-L72):预测值四舍五入后,用
np.packbits把 0/1 位串打包回整数再比对,直接算出加法准确率。
值得注意的一个细节:注释掉的 RNN 版本(L76)特意给权重加了SmallNorm约束(见 mla/neuralnet/constraints.py)来抑制梯度爆炸,而 LSTM 版本直接用LSTM(16)就能稳定训练——这本身就是两个模型能力差异的直观证明。
实战二:文本生成——模仿尼采的写作风格
第二个示例 nnet_rnn_text_generation.py 让网络学习尼采著作的语料,然后自动"续写"出风格相近的文字(思路与 Keras 官方lstm_text_generation示例一致)。
1️⃣ 语料准备
mla/datasets/base.py 的load_nietzsche(L58-L78)读取 nietzsche.txt,把文本切成大量"40 个字符的句子 + 第 41 个字符作为标签"的滑动窗口对,并把字符 one-hot 编码成(句数, 40, 字符表大小)的矩阵。语料文件就在 mla/datasets/data/nietzsche.txt。
2️⃣ 模型与训练
结构为LSTM(128, return_sequences=False)→Dense(字符数)→softmax,损失用categorical_crossentropy,优化器 RMSprop(L44-L58)。外层用 for 循环反复训练 25 轮,每轮都从原文随机抽一句当"种子"现场生成,可以直观感受模型随训练逐步"像人话"的过程。
3️⃣ 温度采样
sample函数(L22-L29)是文本生成的灵魂:把预测概率取对数、除以温度temperature再 softmax,最后按概率抽样。示例中temperature=0.5让分布更"尖",生成的文字更确定;调大到 1.0 以上则更随机、更有"创造力"。
快速上手:三步跑通两个示例
git clone https://gitcode.com/gh_mirrors/ml/MLAlgorithms cd MLAlgorithms pip install scipy numpy && pip install -e .然后任选其一运行(无需安装也可直接以模块方式运行):
python -m examples.nnet_rnn_binary_add python -m examples.nnet_rnn_text_generation不想本地配置?项目还附带 Dockerfile,一行docker build -t mlalgorithms .即可在容器里跑全部示例。
两个示例对比与学习建议
| 维度 | 二进制加法 | 文本生成 |
|---|---|---|
| 任务类型 | 逐位回归(0/1) | 字符级分类 |
| 输出层 | TimeDistributedDense+ sigmoid | Dense+ softmax |
| 损失函数 | mse | categorical_crossentropy |
| 关键技巧 | 低位在先的位序设计 | 温度采样控制随机性 |
建议的学习路径:
- 先读 rnn.py:只约 110 行,逐行推一遍前向/反向的张量形状;
- 再读 lstm.py:对照四个门的命名注释理解参数结构;
- 跑通两个示例:把
LSTM换成RNN、把temperature调来调去,观察训练曲线和生成质量的变化——亲手实验比看十篇博客都有效。
当你真正读懂了这两份代码,再回头看框架里"黑盒"的RNN、LSTM模块时,你会发现它们不过就是这几行循环的封装罢了。🎯
【免费下载链接】MLAlgorithmsMinimal and clean examples of machine learning algorithms implementations项目地址: https://gitcode.com/gh_mirrors/ml/MLAlgorithms
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考