第 29 课 从固定窗口走到 RNN、LSTM 和 GRU¶
行情总是按时间到来。这一课从一段被窗口丢掉的历史开始,逐日追踪 RNN 的状态,再把 LSTM 的遗忘、写入和读出拆成可以手算的动作。
被五日窗口挡在门外的财报¶
假设某公司六个交易日前公布财报,价格当天跳涨 8%,成交量也明显放大。随后五天的收益依次为 -1%、2%、1%、-1% 和 1%。研究者想用最近五日行情预测明天,但他制作的输入只有下面五个数字。
财报日已经落在窗口之外,模型完全看不见。把窗口扩大到六十日可以保留更多历史,却会出现新的困难。输入列从“前一日收益”一直排到“前六十日收益”,同一种形状出现在不同位置时,普通网络需要分别学习;窗口越长,输入维度越大,可用的完整样本反而越少,过时信息也会一起进入。
固定窗口本身没有错。许多可靠策略都使用明确的近二十日或近六十日统计。问题在于,有些研究任务更像阅读一篇按时间展开的故事。我们希望模型每天读入一页,把值得保留的内容压缩成一份状态,第二天继续带着这份状态阅读。循环神经网络正是为这种顺序处理设计的。

上图展示四种历史读取方式。固定窗口只保留预先选定的最近几格;RNN 每读一格就更新一枚状态;LSTM 为长期状态增加保留、写入和读出控制;注意力模型则允许当前位置直接比较较远的历史位置。前三种是本课重点,最后一种会在第 30 课展开。
RNN 比普通神经元多带了一份昨日状态¶
普通前馈网络处理今天的输入时,不会自动记得昨天内部算出了什么。RNN 在每天计算结束后留下一个隐藏状态,第二天把新输入和旧状态一起交给同一个计算单元。这里的“隐藏”表示它是模型内部的中间数;这里的“状态”表示它会沿时间继续传递。
先不看矩阵公式,我们设计一条可以手算的更新规则。每天的新状态等于旧状态的 60%,再加上当天收益。为了避免小数太长,表格把 1% 暂时记作数字 1。初始状态设为 0。
把五天逐步算完,会得到下面这张状态账本。
| 日期 | 今天的收益 | 昨天的状态 | 先保留旧状态的 60% | 今天的新状态 |
|---|---|---|---|---|
| 第 1 天 | -1 | 0.00 | 0.00 | -1.00 |
| 第 2 天 | +2 | -1.00 | -0.60 | 1.40 |
| 第 3 天 | +1 | 1.40 | 0.84 | 1.84 |
| 第 4 天 | -1 | 1.84 | 1.10 | 0.10 |
| 第 5 天 | +1 | 0.10 | 0.06 | 1.06 |
第 5 天的状态 1.06 并非某一天收益的原样复制。它包含当天的 1,也包含前四天经过多次衰减后的影响。以第 2 天的上涨 2 为例,它经过第 3、4、5 天三次乘以 0.6,到最后仍留下 \(2\times0.6^3=0.432\) 的贡献。RNN 用一个固定长度的状态压缩历史,而没有保存每一天的完整原始记录。
真实模型会同时处理动量、成交量、波动和其他特征,隐藏状态也会从一个数扩展成一组数。更新比例不会固定写成 0.6,而是由训练数据学到的权重决定。为了让数值保持在可控范围,基础 RNN 常用双曲正切函数,英文写作 tanh。它会把很大的正负输入压到 -1 与 1 之间,同时保留正负方向。
现在公式才容易阅读。当天输入记作 \(x_t\),昨日隐藏状态记作 \(h_{t-1}\),今日状态记作 \(h_t\)。\(W_x\) 负责调整当天输入,\(W_h\) 负责调整旧状态,\(b\) 是偏置。
同一组 \(W_x,W_h,b\) 会在每个日期重复使用。图中看起来有五个状态节点,实际可以理解为同一台机器连续工作五次。把循环计算画成从第 1 天到第 5 天的一条链,叫作按时间展开。
flowchart LR
accTitle: RNN 用同一套规则沿五天更新状态
accDescr: 每一天的新输入与前一天隐藏状态共同产生今天隐藏状态,第五天状态再用于预测下一阶段标签
x1["第 1 天输入 x1"] --> h1["状态 h1"]
h1 --> h2["状态 h2"]
x2["第 2 天输入 x2"] --> h2
h2 --> h3["状态 h3"]
x3["第 3 天输入 x3"] --> h3
h3 --> h4["状态 h4"]
x4["第 4 天输入 x4"] --> h4
h4 --> h5["状态 h5"]
x5["第 5 天输入 x5"] --> h5
h5 --> y["预测未来标签"]
状态是怎样变成预测的¶
如果任务是用过去六十日预测下一周收益,网络通常取最后一天的隐藏状态,把它交给一个普通输出层。输出层可能给出连续收益、上涨概率或横截面分数。只在序列结束后输出一次的结构常被称为“多对一”,意思是多天输入对应一个答案。
另一些任务需要每天都输出。例如,用每分钟行情判断下一分钟市场状态,序列中的每个时间点都可能产生标签。这类结构常被称为“多对多”。名称描述的是输入与输出的数量关系,不代表哪一种更先进。选择方式取决于研究问题和标签时间。
无论使用哪一种输出,成交边界仍与前面课程相同。若第 \(t\) 日收盘后才能得到完整序列,预测最早只能在随后可执行的时点下单。把最后一个输入日的收盘价同时当成成交价,会让回测提前行动。序列模型增加了结构复杂度,却不会修改市场时间规则。
RNN 为何难以记住很久以前¶
刚才的教学规则每经过一天,就把旧影响乘以 0.6。第 1 天的一单位信息,十天后只剩 \(0.6^{10}\),约等于 0.006;三十天后几乎无法分辨。前向计算中的信息会衰减,训练时从最后一天传回最早一天的改进信号也会经历类似连乘。
第 28 课把这种改进信号称为梯度。RNN 的反向传播需要沿时间链逐步计算,所以又常叫作随时间反向传播。当许多步的变化率都小于 1,远处梯度经过连乘后越来越接近零。早期事件几乎收不到学习信号,模型很难知道应该怎样修改处理它的权重。这种现象叫作梯度消失。
若连乘中的数经常大于 1,梯度可能快速膨胀。一个轻微误差传回几十步后变成巨大数值,参数更新会剧烈震荡,甚至出现无穷大或非数值。这叫作梯度爆炸。梯度裁剪可以给梯度设置上限,缓解爆炸问题,却不能恢复已经消失的长期信号。
基础 RNN 还有一项直观限制。新旧信息每天都被挤进同一组状态,没有专门的开关告诉模型哪些内容应长期保留,哪些噪声应立即丢弃。重大财报与普通日内波动可能竞争有限的状态空间。LSTM 的设计正是从这里出发。
LSTM 给长期记忆单独留出一条通道¶
LSTM 的中文常译为长短期记忆网络。它保留 RNN 的隐藏状态 \(h_t\),又增加一组叫作细胞状态的数 \(c_t\)。可以把细胞状态想成一条持续向后传递的笔记带。每天到来时,模型依次处理三个问题。旧笔记还要保留多少,今天的新信息要写入多少,当前预测需要读出多少。
这三个比例分别由遗忘门、输入门和输出门控制。门值落在 0 与 1 之间。接近 0 表示几乎关闭,接近 1 表示大部分通过。研究者不会把门值作为交易参数手工指定,模型会根据当天输入、上一隐藏状态和训练目标学习这些内部控制量。
flowchart LR
accTitle: LSTM 一天之内的保留、写入和读出
accDescr: 旧细胞状态先经过遗忘门保留一部分,当天候选记忆再经过输入门写入,新的细胞状态最后经过输出门形成隐藏状态
oldc["旧细胞状态 c(t-1)<br/>昨天留下的长期笔记"] --> forget["遗忘门 f(t)<br/>决定保留比例"]
x["当天输入 x(t)<br/>与旧隐藏状态"] --> forget
x --> input["输入门 i(t)<br/>决定写入比例"]
x --> candidate["候选记忆 c~(t)<br/>准备写入的内容"]
forget --> newc["新细胞状态 c(t)<br/>保留项加写入项"]
input --> newc
candidate --> newc
newc --> output["输出门 o(t)<br/>决定当前读出比例"]
x --> output
output --> h["隐藏状态 h(t)<br/>交给预测与下一天"]
第一道门决定旧笔记保留多少¶
假设昨日细胞状态为 0.80。今天出现普通波动后,遗忘门给出 0.90。旧记忆保留部分就是 \(0.80\times0.90=0.72\)。门值若为 0.10,旧记忆只留下 0.08。遗忘并不总是损失。市场制度、公司基本面和波动环境都会变化,持续保留过时关系同样会伤害预测。
遗忘门根据当天输入和昨日隐藏状态计算一个线性分数,再经过上一课见过的 Sigmoid 函数,把结果压到 0 与 1 之间。写成公式如下。
方括号表示把当天输入和旧隐藏状态放在一起。\(W_f\) 与 \(b_f\) 是训练中学习的权重和偏置。\(f_t\) 通常是一组数,因此模型可以让某些记忆维度长期保留,同时让另一些维度迅速衰减。
第二道门决定今天写多少,以及写什么¶
写新笔记需要回答两个不同问题。输入门决定“允许写入多少”,候选记忆决定“准备写入什么”。假设候选记忆为 -0.50,输入门为 0.20,实际写入量就是 \(-0.50\times0.20=-0.10\)。负号可以表示对某个内部方向的削弱,并不自动等于做空信号。
输入门继续使用 Sigmoid 产生 0 到 1 的比例,候选记忆通常使用 tanh 产生 -1 到 1 的内容。
新的细胞状态把“旧状态保留项”和“新候选写入项”相加。沿用刚才的数字,旧状态保留 0.72,新内容写入 -0.10,所以今天的细胞状态为 0.62。
这条加法通道是 LSTM 的关键。遗忘门长期接近 1、输入门保持较小时,旧状态可以跨越许多日期相对稳定地传递。梯度也能沿这条通道保留更多影响,因此 LSTM 比基础 RNN 更容易学习远距离关系。
第三道门决定今天读出多少¶
细胞状态保存的内容不必全部用于今天的预测。输出门决定当前读出比例。假设新细胞状态为 0.62,经过 tanh 后约为 0.55,输出门为 0.60,今日隐藏状态约为 \(0.60\times0.55=0.33\)。细胞状态 0.62 仍会继续传向下一天,隐藏状态 0.33 则参与今天输出和下一天各道门的计算。
把这一天的动作合在一张数字卡里,可以看到 LSTM 没有神秘步骤。它仍由乘法、加法和激活函数组成,只是把信息流拆得更细。
| 动作 | 输入数值 | 计算 | 结果 |
|---|---|---|---|
| 保留旧记忆 | 旧状态 0.80,遗忘门 0.90 | 0.80 × 0.90 | 0.72 |
| 准备新内容 | 候选记忆 -0.50 | 暂不写入 | -0.50 |
| 写入新内容 | 输入门 0.20 | -0.50 × 0.20 | -0.10 |
| 更新细胞状态 | 保留项 0.72,写入项 -0.10 | 0.72 - 0.10 | 0.62 |
| 形成隐藏状态 | tanh(0.62) 约 0.55,输出门 0.60 |
0.55 × 0.60 | 约 0.33 |

这张图把细胞状态画成贯穿时间的主通道。遗忘门控制旧信息的保留,输入门和候选记忆共同完成写入,输出门只控制当前向外暴露多少。图中的门是计算比例的函数,并非有人在模型内部手动开关。
LSTM 仍然会忘记,也仍然会过拟合¶
门控改善了长期训练,却没有保证模型一定保留研究者认为重要的事件。如果训练标签与早期事件关系微弱,模型可能学会关闭相应通道;如果样本中存在泄漏,门控反而能更有效地保存错误答案。序列太长时,计算成本和参数选择也会增加。
门值有时可以画成热力图,观察哪些日期出现较高保留或写入比例。这种图只描述模型内部的信息流。若要把某一维命名为“趋势记忆”或“风险情绪”,还要用删除特征、替换日期、跨窗口复现等独立实验支持。仅凭颜色变化编出市场故事,很容易把解释建立在偶然相关上。
GRU 如何把三道门简化成两道¶
GRU 的中文常译为门控循环单元。它把 LSTM 的细胞状态和隐藏状态合并成一组状态,主要使用更新门与重置门。更新门决定旧状态保留多少、新候选接收多少;重置门决定生成候选状态时参考多少过去。
flowchart LR
accTitle: GRU 用两道门更新一组状态
accDescr: 重置门控制生成候选状态时参考多少旧信息,更新门再在旧状态和新候选之间分配比例,得到今天的新状态
old["旧状态 h(t-1)"] --> reset["重置门<br/>候选生成时参考多少过去"]
input["当天输入 x(t)"] --> candidate["候选状态"]
reset --> candidate
old --> update["更新门<br/>分配新旧比例"]
candidate --> update
update --> new["新状态 h(t)"]
可以把更新门想成一个混合旋钮。若它更偏向旧状态,记忆会保持得久;若它更偏向新候选,模型会快速响应当天信息。重置门较小时,候选状态更少参考过去,适合在环境突变时重新开始组织信息。
GRU 的参数通常少于相近规模的 LSTM,训练速度可能更快,在数据有限时也更容易控制。LSTM 把长期状态与当前输出分开,提供更细的读写控制。两者没有脱离任务的固定胜负。公平比较必须使用相同输入、标签、时间切分、训练预算、随机种子清单和交易规则。
把 RNN、LSTM 与 GRU 放回同一张桌子¶
| 模型 | 状态怎样传递 | 主要优点 | 主要限制 | 合适的起点 |
|---|---|---|---|---|
| 基础 RNN | 一组隐藏状态反复更新 | 结构简单,容易看清循环逻辑 | 长距离梯度容易消失或爆炸 | 短序列与教学基线 |
| LSTM | 隐藏状态加细胞状态,并用三道门控制 | 长期信息通道更稳定,读写分工细 | 参数较多,训练和解释成本更高 | 长短信息并存且样本较充足 |
| GRU | 合并状态,并用更新门和重置门控制 | 参数较少,训练通常较轻 | 记忆控制粒度比 LSTM 简化 | 数据有限时的门控基线 |
“基线”在这里仍然重要。固定窗口加线性模型、固定窗口加树模型、基础 RNN 都可以成为参照。如果 LSTM 只比它们多消耗十倍计算,却没有在滚动测试中提供稳定增量,就没有必要因为名称更复杂而保留。
序列数据在程序里长什么样¶
普通表格通常有“样本数”和“特征数”两个方向。序列模型还要知道每个样本内部有多少个时间步,因此输入常写成三个维度,顺序是 [样本数, 时间长度, 每日特征数]。
假设共有 2,000 个样本,每个样本包含过去 60 日,每日记录收益、成交量变化和波动三项特征,输入形状就是 [2000, 60, 3]。第一维告诉模型有多少段历史,第二维告诉它每段有多少天,第三维告诉它每天观察多少项数值。
建立这些窗口会损失一部分样本。资产至少要拥有 60 日有效历史,才能形成第一个输入;上市时间不足、长期停牌或字段缺失都会减少可用数量。相邻窗口还会共享 59 天行情,彼此高度相似。若随机把它们分到训练集和测试集,两边会出现近乎重复的序列,测试结果因此虚高。序列任务更需要按照时间边界切分,并结合标签跨度设置隔离区。
第一段序列模型代码怎样读¶
下面的示例完成一个“六十日输入对应一个连续标签”的任务。X_train 的形状应为“训练样本数、60、每日特征数”,y_train 每项对应同一行序列之后的未来标签。X_valid 与 y_valid 来自更晚时期。运行后,每个验证样本应得到一个预测。
from tensorflow.keras import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.callbacks import EarlyStopping
model = Sequential([
LSTM(16, input_shape=(60, X_train.shape[2])),
Dense(1)
])
model.compile(optimizer="adam", loss="mse")
stop = EarlyStopping(
monitor="val_loss",
patience=5,
restore_best_weights=True
)
model.fit(
X_train,
y_train,
validation_data=(X_valid, y_valid),
epochs=100,
batch_size=64,
callbacks=[stop],
verbose=0
)
prediction = model.predict(X_valid)
LSTM(16, ...) 表示隐藏状态和细胞状态各有 16 个内部维度。input_shape 只描述一个样本内部的 60 天和每日特征数,不把样本数量写进去。Dense(1) 把最后状态转换成一个连续预测。均方误差、早停和批次的含义与第 28 课相同。
读代码时还要寻找没有写在模型结构里的选择。六十天为何合适,缺失日期如何处理,跨资产序列怎样对齐,标准化参数从哪里估计,标签从哪个可成交时点开始,验证边界是否隔离了重叠标签。网络只会接收准备好的数组,这些数组怎样形成往往比 16 个还是 32 个单元更影响可信度。
若把 LSTM 换成 GRU,代码改动很小,研究结论却不能只比较一次运行。两者的参数量不同,随机初始化也会带来波动。应预先设定多个容量和随机种子,在同一滚动验证框架下报告平均、范围和失败次数,并把固定窗口基线一起保留。
序列长度为什么不能越长越好¶
使用过去二十日、六十日还是二百五十日,代表不同研究假设。短窗口更关注近期状态,样本数量较多,计算也轻;长窗口可以覆盖慢变化,却会纳入更多过时关系,减少拥有完整历史的资产,并增加训练难度。
序列长度应先从机制提出一个有限候选范围。例如,研究月度趋势可以比较二十日与六十日,研究年度基本面变化才可能考虑更长跨度。随后只在训练与验证窗口选择,测试集保持封存。如果先看测试结果,再挑出“恰好最好”的六十七天,窗口长度就成为过度拟合的一部分。
还要区分交易频率与记忆长度。每天更新模型不代表只能看一天历史;使用一年历史也不代表持仓一年。输入跨度、预测跨度、调仓频率和持有期限是四个不同决定,必须在研究说明中分别写清。
容易弄错的地方¶
| 容易出现的理解 | 为什么不够可靠 | 应该怎样处理 |
|---|---|---|
| 隐藏状态就是模型对市场的理解 | 它只是为降低损失形成的内部数字表示 | 用替换输入、跨期复现和消融实验谨慎解释 |
| 窗口越长,信息越完整 | 长窗口减少样本,也会带入过时内容 | 从机制给出有限候选,在验证期选择 |
| LSTM 一定胜过 GRU | 参数更多只表示可表达关系更多 | 在相同数据、预算和种子下比较 |
| 随机切分重叠窗口 | 相邻样本共享大部分日期,测试会看到近似副本 | 按时间切分,并处理标签跨度重叠 |
| 门值高就代表看多信号 | 门值只控制内部维度的信息流 | 不把单个门值直接转成交易方向 |
| 用测试集决定序列长度和早停 | 测试答案参与了模型选择 | 所有选择停留在训练与验证阶段 |
| 只与另一个深度模型比较 | 两者可能一起输给简单方案 | 始终保留固定窗口线性与树模型基线 |
一分钟自测¶
旧细胞状态为 0.6,遗忘门为 0.8,候选记忆为 -0.4,输入门为 0.25。新细胞状态是多少?若输出门很小,细胞状态是否会立刻消失?
查看答案
保留项为 `0.6 × 0.8 = 0.48`,写入项为 `-0.4 × 0.25 = -0.10`,新细胞状态为 0.38。输出门只控制当前隐藏状态读出多少,细胞状态仍可继续传向下一天,不会因为输出门小就立刻消失。本课术语¶
| 词语 | 先这样理解 |
|---|---|
| 序列 | 按时间或其他顺序排列的一组数据 |
| 隐藏状态 | 循环模型传给下一时间步的压缩信息 |
| RNN | 重复使用同一计算单元逐步处理序列的网络 |
| 按时间展开 | 把同一循环单元画成多个连续日期的表示方法 |
| 梯度消失 | 远距离学习信号经过连乘后越来越小 |
| 细胞状态 | LSTM 中用于较稳定传递长期信息的通道 |
| 门控 | 用 0 到 1 的比例调节信息保留、写入或读出 |
| LSTM | 使用细胞状态和三类门控的循环网络 |
| GRU | 用更新门与重置门简化状态控制的循环网络 |
| 多对一 | 多个时间步输入对应一个最终输出的任务结构 |
课后练习¶
-
状态跟踪。 使用“新状态等于 0.5 乘以旧状态再加当天收益”,初始状态为 0,两天收益依次为 2 和 -1。第二天状态是多少?
提示与答案
第一天状态为 2。第二天代入以后得到 `0.5 × 2 - 1 = 0`。 -
长期衰减。 某项影响每天保留 80%。请计算五天后还剩原来的多少,并解释这对基础 RNN 意味着什么。
提示与答案
`0.8` 的五次方约为 0.328,也就是约 32.8%。时间继续拉长后影响会越来越小,早期事件可能难以收到足够训练信号。 -
门控计算。 旧状态为 0.7,遗忘门为 0.9,候选记忆为 0.4,输入门为 0.5。请算出新细胞状态。
提示与答案
旧信息保留 `0.7 × 0.9 = 0.63`,新信息写入 `0.4 × 0.5 = 0.20`,相加得到 0.83。 -
动手实验。 在序列记忆实验中放入一个较早的重大事件,逐步把它移得更远,比较固定窗口、简化 RNN 和带高遗忘门的 LSTM。
提示与答案
事件离开固定窗口后会完全不可见;简化 RNN 的影响会逐步衰减;LSTM 在遗忘门长期接近 1 时可保留更久。实验结论只解释信息通路,不能直接证明真实市场预测更好。 -
研究设计。 你要比较固定窗口树模型、RNN、LSTM 和 GRU。请设计一套公平方案,并说明至少六项必须固定或报告的条件。
评价要点
应包括相同数据版本、特征、标签、时间切分、成交规则和成本;控制相近参数量或训练预算;预先固定多个随机种子;只在验证期选择序列长度与早停;报告平均、范围、失败次数和不同市场阶段结果。
固定窗口会把更早行情挡在输入之外,RNN 用逐日更新的状态把过去带向今天,长链条中的信息与梯度却会逐步变弱。LSTM 用保留、写入和读出控制长期状态,GRU 再把状态和门作了简化。把五日数字从头算一遍,比只记住三个缩写更能说明这些模型各自改了哪里。