跳转至

第 28 课 神经网络从一颗神经元开始

这一课不急着追赶复杂结构。我们先把一颗神经元的计算完整做一遍,再沿着预测误差倒回去,看清网络究竟怎样学习。

适合谁已经理解线性模型和树模型,第一次接触神经网络的读者
前置知识特征、标签、系数、损失以及训练与验证的时间边界
建议用时120 分钟
本课成果手算一个小型网络,读懂训练循环,并判断复杂度是否值得保留

一条直线遇到拐弯以后

第 26 课的线性模型会给每项特征乘一个系数,再把结果相加。这种方法清楚而稳定,但它倾向于让一项特征在整个取值范围内保持同一个方向。真实市场经常出现拐弯。例如,适度上涨可能代表趋势正在形成,短期暴涨却可能伴随拥挤和回撤风险;低波动有时意味着稳定,高到异常的平静也可能是流动性不足造成的假象。

假设我们把一项股票的二十日动量与下一周平均收益整理成下面的教学表。这些数字只为说明形状,并不代表真实市场规律。

二十日动量区间 样本中的下一周平均收益 一个可能的解释
明显为负 -0.4% 下跌仍在延续
接近零 0.0% 缺少清晰方向
温和为正 0.5% 趋势较健康
极高 -0.2% 拥挤与短期反转风险上升

如果只画一条直线,很难同时表达“先上升、后来下降”的弯曲关系。决策树可以用多个门槛切出区间,神经网络则提供另一种办法。它先让许多小计算单元分别寻找局部形状,再把这些局部结果组合起来。每个小计算单元通常叫作人工神经元。这个名称来自早期类比,程序里的神经元本质上仍是一组乘法、加法和函数变换。

一颗神经元其实是一台小计算器

我们先给神经元两项已经标准化的输入。第一项是动量,数值为 0.4;第二项是波动,数值为 0.2。标准化后的 0.4 表示动量高于训练样本平均水平,但还不到一个标准差。神经元给动量设置权重 0.8,给波动设置权重 -0.5,另有一个偏置 -0.1。

手算过程分为三步。先用 0.4 乘以 0.8,得到 0.32;再用 0.2 乘以 -0.5,得到 -0.10;最后加上偏置 -0.1,得到 0.12。权重承担的工作很像线性回归系数,偏置承担的工作很像截距。把两项输入记作 \(x_1,x_2\),权重记作 \(w_1,w_2\),偏置记作 \(b\),刚才的加权结果可以写成下面的公式。

\[ z=w_1x_1+w_2x_2+b \]

到这里,神经元与线性模型仍然没有本质差别。关键发生在下一步。神经元把 0.12 交给一个叫作激活函数的变换。常见的 ReLU 激活规则很简单。输入为正就保留原数,输入为负或零就输出零。

\[ \operatorname{ReLU}(z)=\max(0,z) \]

刚才的 0.12 会原样通过。若另一项资产算出的 \(z\) 为 -0.3,ReLU 输出就是 0。可以把它想成一道只允许正信号通过的门。不同神经元使用不同权重和偏置,门打开的位置也不同,于是它们能分别响应输入空间里的不同区域。

激活函数为何必要,可以用一个代数事实理解。假设第一层只做线性加权,第二层也只做线性加权,两层合并后仍能整理成一组新的系数和截距。堆叠很多层也不会改变这一点。加入 ReLU 等非线性激活后,网络才有机会拼出折线、弯曲边界和特征之间更复杂的组合。

两颗神经元怎样合作完成一次预测

继续使用动量 0.4 和波动 0.2。隐藏层安排两颗神经元。神经元甲偏好较高动量和较低波动,神经元乙使用另一组权重观察不同方向。完整手算如下。

神经元甲的线性结果 = 0.8 × 0.4 - 0.5 × 0.2 - 0.1 = 0.12
神经元甲经过 ReLU = 0.12

神经元乙的线性结果 = -0.4 × 0.4 + 0.9 × 0.2 + 0 = 0.02
神经元乙经过 ReLU = 0.02

输出分数 = 1.5 × 0.12 - 0.5 × 0.02 + 0 = 0.17

原来的两项特征经过隐藏层后,变成 0.12 和 0.02 两个中间数。输出层再给它们乘上新的权重,得到最终分数 0.17。“隐藏层”只是表示这一层位于原始输入与最终输出之间,它的数值没有作为人工标签直接交给模型。我们依然可以查看这些数,却不能因为某颗神经元经常在上涨前变大,就随意把它命名为“趋势信心”。它可能同时混合了动量、波动和样本分布的多种影响。

flowchart LR
    accTitle: 两层前馈神经网络的计算路线
    accDescr: 三项输入分别连接到多个隐藏神经元,隐藏神经元进行加权、加偏置和激活,随后由输出层合成一个预测分数
    x1["动量 x1"] --> h1["隐藏单元 1<br/>加权后经过 ReLU"]
    x1 --> h2["隐藏单元 2<br/>加权后经过 ReLU"]
    x2["波动 x2"] --> h1
    x2 --> h3["隐藏单元 3<br/>加权后经过 ReLU"]
    x3["质量 x3"] --> h2
    x3 --> h3
    h1 --> y["输出层<br/>合成预测值"]
    h2 --> y
    h3 --> y

同一层中的神经元可以并行计算,下一层只能在上一层结果出来后继续。输入沿着这个方向走到输出的过程叫作前向传播。如果连接没有回到更早的节点,这类结构通常叫作前馈神经网络。第 29 课会再讨论带有时间记忆的循环结构。

网络如何知道自己的答案差多少

一次前向传播只产生预测,还没有发生学习。学习需要拿预测与真实标签比较。若刚才的 0.17 是下一期收益预测,而真实标签为 0.10,残差为 0.07。使用平方误差时,本项样本的损失是 \((0.17-0.10)^2=0.0049\)。损失函数把预测质量压缩成一个可比较的数,数越小,表示按照当前规则,预测与标签越接近。

不同任务会使用不同损失。连续收益预测可以从均方误差开始,涨跌分类常用上一课介绍的交叉熵损失。损失函数决定模型会重点纠正哪些错误。均方误差对极端偏差特别敏感,交叉熵会重罚自信却错误的分类。选择损失时要结合标签含义和策略目的,不能只因为某个函数在示例代码里常见就照搬。

假设我们暂时只改输出层里连接神经元甲的权重。它原来是 1.5,把它试着降为 1.4 后,预测变成 \(1.4\times0.12-0.5\times0.02=0.158\)。新的平方误差约为 0.0034,比原来的 0.0049 小。这个试算告诉我们,当前样本下把该权重向下调一点有帮助。

真实网络可能包含成千上万个参数,无法逐个尝试。程序会计算每个参数轻微变化时,最终损失朝哪个方向、以多快速度变化。这个变化率叫作梯度。把输出误差沿着连接逐层传回去,计算所有参数梯度的过程叫作反向传播

反向传播依赖数学中的链式法则。可以把它理解成一份责任账单。输出层先判断哪些连接把预测推高或压低了多少,然后把影响继续分摊给上一层,直到回到最早的权重。它不会判断某项金融逻辑是否合理,只会按照损失函数和样本数据分配数值责任。

优化器怎样让参数走下一小步

有了梯度,还需要决定参数移动多少。负责更新参数的规则叫作优化器。最基础的梯度下降会让参数朝着损失减小的方向移动,移动幅度由学习率控制。学习率像步长。步子太大,参数可能跨过较好位置,在两边来回震荡;步子太小,训练会很慢,在有限轮数里可能走不到有用区域。

常见的 Adam 优化器会根据近期梯度自动调整不同参数的步幅。它通常是方便的起点,却不会替研究者解决数据泄漏、标签错误或样本不足。更先进的优化器也不能把噪声变成可交易信号。

训练数据往往分成许多小组,每一小组叫作一个批次。模型用一个批次完成前向传播、计算损失、反向传播和参数更新,再处理下一批。把全部训练样本大致看过一遍叫作一个轮次,英文常写作 epoch。若有 10,000 个样本,每批 100 个,一个轮次大约会进行 100 次参数更新。

批次过小,梯度变化更嘈杂;批次过大,会占用更多内存,也可能失去随机更新带来的帮助。量化时间序列还要注意批次如何生成。标准化参数、标签和任何窗口统计仍须遵守时间边界,不能因为训练框架会自动打乱样本,就把未来信息混入早期特征。

神经网络通过预测、损失与参数更新学习

上图把一次训练画成可以反复执行的循环。特征先沿网络生成预测,预测与已知标签形成损失,反向传播再把改进方向送回参数。这个过程可以重复很多次,验证数据却只负责观察模型对未参与更新的时期是否仍然有效。验证样本不能被送进优化器更新权重。

一段网络代码应当先读哪些地方

下面的示例完成一个小任务。输入 X_train 是训练期的标准化特征矩阵,每一行对应一个样本,每一列对应一项特征;y_train 是同一批样本的连续标签。X_validy_valid 来自更晚的验证时期。我们期望模型输出每个样本一个预测,并同时记录训练损失与验证损失。

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.callbacks import EarlyStopping

model = Sequential([
    Dense(8, activation="relu", input_shape=(X_train.shape[1],)),
    Dense(4, activation="relu"),
    Dense(1)
])

model.compile(optimizer="adam", loss="mse")
stop = EarlyStopping(
    monitor="val_loss",
    patience=5,
    restore_best_weights=True
)

history = model.fit(
    X_train,
    y_train,
    validation_data=(X_valid, y_valid),
    epochs=100,
    batch_size=128,
    callbacks=[stop],
    verbose=0
)

前三个 Dense 定义网络结构。第一隐藏层有 8 颗神经元,第二隐藏层有 4 颗,二者都使用 ReLU。最后一层只有一个输出,也没有把结果限制在 0 到 1,因此适合连续数值预测。若任务改成二分类,输出层和损失函数也要一起调整,不能只更换标签。

compile 指定 Adam 优化器和均方误差。EarlyStopping 观察验证损失。如果连续 5 个轮次没有改善,训练就停止,并恢复到验证损失最好时的参数。fit 才真正开始训练。它最多运行 100 个轮次,每批使用 128 个样本,同时把更晚的验证数据交给监视器。

运行后应先检查 history.history["loss"]history.history["val_loss"]。两条曲线都很高,模型可能尚未学到规律或数据尺度存在问题;训练损失持续下降而验证损失回升,通常说明网络开始贴合训练噪声;两条曲线突然出现无穷大或非数值,可能与学习率、异常输入或数值范围有关。只看最后一个预测文件,容易错过这些信号。

过拟合在训练曲线上怎样出现

假设前十个轮次中,训练损失从 0.012 降到 0.006,验证损失也从 0.013 降到 0.008。继续训练到第三十轮,训练损失降到 0.002,验证损失却升回 0.011。模型在训练样本上越来越准确,在较晚数据上反而更差,这就是典型的过拟合迹象。

神经网络参数多,能够记录非常细的样本差异。控制过拟合的方法各有针对性。减少层数或神经元会直接缩小模型容量;权重衰减会限制参数过大;Dropout 会在训练时随机关闭一部分连接,迫使网络减少对单一路径的依赖;早停会在验证表现不再改善时结束训练。增加真正独立的历史环境也有帮助,但把相邻高度重叠的窗口复制得更多,并不等于获得了同样多的新信息。

方法 它限制了什么 使用时要检查什么
缩小网络 减少可以记忆的细节数量 简化后验证表现是否接近,波动是否变小
权重衰减 限制少数连接拥有极端权重 强度是否只在训练与验证阶段选择
Dropout 降低网络对固定连接组合的依赖 训练阶段和预测阶段的行为是否正确切换
早停 阻止训练损失无限下降时继续拟合 监视的是验证集,测试集是否保持封存
更多独立样本 覆盖更多市场阶段和资产状态 新样本是否真的提供了新的时间信息

这些方法只能约束模型学习方式。若标签使用了无法成交的未来价格,或全历史标准化泄漏了未来分布,正则化只会让错误学习得慢一些。数据边界始终比网络结构更优先。

为什么同一段代码每次可能略有不同

网络权重通常从随机数开始,批次顺序也可能随机变化。同一套数据和结构运行两次,最后参数与结果可能不完全相同。固定随机种子有助于复现某次实验,却不能证明这一种结果具有代表性。

较稳妥的做法是提前确定若干种子,用相同数据切分和训练规则重复运行,报告平均结果、变化范围与失败次数。若模型只在一个种子上表现良好,其余运行全部消失,这种脆弱性本身就是研究结论。挑出最好的一次再报告,会把偶然波动伪装成稳定能力。

硬件和软件版本也可能带来轻微差异。研究记录应保存数据版本、特征清单、随机种子、网络结构、损失函数、优化器、学习率、批次大小和停止规则。完整记录可以帮助他人还原研究选择,并判断数值差异来自哪里;即使个别小数位没有完全相同,研究过程依然能够被检查。

什么时候值得使用神经网络

神经网络擅长表达大量输入之间的非线性交互,也能处理图像、文本和长序列等复杂数据。若我们只有几十项结构化因子和有限历史,树模型与线性模型往往已经是很强的基线。网络增加了训练随机性、参数数量、计算成本与解释难度,必须用稳定的样本外增量为这些负担提供理由。

评估时应保持比较公平。不同模型使用相同特征、相同时间切分、相同成交规则和相近的调参预算。随后同时比较预测误差、横截面排序、扣费后组合、不同市场阶段表现和多次随机训练的范围。若网络只在训练期领先,或微小增量被十倍换手吃掉,保留简单模型通常更可靠。

神经网络也不天然懂时间。把今天、昨天和前天的数值平铺成普通列,它只会把这些列当作不同输入。下一课将从这个限制出发,讨论 RNN 为什么携带隐藏状态,LSTM 与 GRU 又为何给记忆增加可学习的门。

容易弄错的地方

容易出现的判断 问题出在哪里 更可靠的做法
层数越多,模型越高级 表达能力与记忆噪声的能力会同时增加 从简单基线开始,只保留稳定的样本外增量
训练损失最低就是最好模型 训练数据已经参与参数更新,不能独立评价 观察更晚的验证期,并封存最终测试期
用测试损失决定早停轮数 测试答案参与了训练选择 早停只看验证集,冻结规则后再测试
固定一个种子就代表稳定 固定种子只复现一次随机路径 预先运行多个种子并报告分布
Dropout 可以修复未来数据 它只能限制连接依赖 回到特征、标签和预处理时间检查
隐藏神经元能直接命名为金融概念 中间表示由损失驱动,可能混合多种信息 用干预、可视化和稳定性实验谨慎解释

一分钟自测

若一个网络包含很多线性层,却完全没有激活函数,它能否表达真正的弯曲关系?训练损失不断下降而验证损失连续上升,又说明了什么?

查看答案 多个纯线性变换合并后仍是一个线性变换,因此不能靠层数得到真正的非线性。训练损失下降、验证损失上升说明模型越来越贴合训练样本,却失去对较晚数据的适应能力,通常是过拟合警报。

本课术语

词语 先这样理解
人工神经元 对输入做加权、加偏置和激活的小型计算单元
隐藏层 位于原始输入与最终输出之间的中间计算层
激活函数 让网络能够表达非线性关系的变换
前向传播 输入逐层计算并得到预测的过程
反向传播 从损失出发计算各项参数影响方向的过程
梯度 参数轻微变化时损失变化的方向与速度
优化器 根据梯度更新参数的规则
学习率 每次参数更新的大致步幅
批次 一次参数更新所使用的一小组样本
轮次 模型大致完整看过一次训练数据

课后练习

  1. 基础计算。 计算 z = 2 × 0.3 - 1 × 0.4 + 0.1,再给出 ReLU 输出。

    提示与答案`z` 等于 0.3。它大于零,因此 ReLU 输出仍是 0.3。

  2. 逐步演算。 某隐藏层有两个输出 0.4 和 0.1,输出层权重分别为 0.5 和 -0.2,偏置为 0.03。最终分数是多少?

    提示与答案逐项相乘再加偏置,结果为 `0.5 × 0.4 - 0.2 × 0.1 + 0.03 = 0.21`。

  3. 排错。 一条训练曲线显示训练损失持续下降,验证损失在第 12 轮后上升。研究者最后选择第 80 轮,并说训练误差最低。请指出问题。

    提示与答案第 80 轮很可能已经过拟合。应按预先设定的早停规则选择验证损失较好的轮次,并让最终测试区间保持独立。

  4. 动手修改。 把示例网络的第一隐藏层从 8 个单元改成 4 个,保持其他条件不变,重复多个种子并记录训练时间、验证损失和变化范围。

    提示与答案只修改 `Dense(8, ...)` 中的单元数。公平比较需要相同数据切分、停止规则和种子清单。若简化后结果相近且波动更小,较小网络更容易维护。

  5. 开放思考。 神经网络的样本外预测略好于逻辑回归,却带来更高换手、更大种子差异和十倍训练时间。你会保留哪一个模型?还需要哪些证据?

    评价要点应讨论扣除成本后的真实增量、不同市场阶段的稳定性、风险暴露、维护成本、故障概率和解释要求。答案没有唯一选择,但不能只引用一个预测指标。

离开本页前
一颗神经元仍然从乘法、加法和函数变换开始,多颗神经元组合以后才有能力表达弯曲关系。训练时先向前得到预测,再计算损失,把改进方向反向传回各层,随后更新参数。训练曲线继续下降而验证曲线转头上升时,模型正在记住训练噪声,这个警报比网络层数更值得先看。