一句话结论:当因子越来越拥挤,人拍脑袋的线性因子越来越难挖。 自编码器(Autoencoder, AE)提供另一条路:它不需要任何收益标签,只靠「把原始特征压缩再解压、逼着网络保留最多信息」这一件事,就能从几十维量价特征里提炼出 5 维「瓶颈编码」——而这 5 维作为新因子,信息密度可以超过原始特征(仿真里组合因子 IC 0.575,vs 原始信息特征 0.333、噪声特征 0.031)。关键在于:它自动把噪声特征「忘掉」了。
一、自编码器到底是什么#
结构是一个「沙漏」:
- 编码: 维 → 12 维();
- 瓶颈: 维 → 维(),信息被强制压缩;
- 解码: 维 → 维()。
训练目标只有一件:让重建误差最小
瓶颈维度 是关键——它逼网络「非把最重要的结构留下不可」。当 远小于 ,网络学不到「逐一复制」,只能学到跨特征的共同变化模式,而这恰恰是因子。
AE 与 PCA 的关系(常被忽略):若去掉所有非线性激活(纯线性 AE),它在数学上等价于 PCA——瓶颈向量就是主成分。加回 /sigmoid,AE 就成了「非线性 PCA / 核 PCA」,能抓住 PCA 抓不到的弯曲流形。所以 AE 不是魔法,是 PCA 的可微、非线性推广。
二、纯 numpy 手写一个自编码器#
不依赖 PyTorch,几十行就能跑通。核心是前向 + 反向(链式法则)+ 小批量 SGD:
import numpy as np
rng = np.random.default_rng(20260711)
N, d, m = 600, 20, 5 # 600 只股票, 20 维特征, 瓶颈 5 维
# --- 构造数据:3 个潜因子驱动收益 + 大量噪声特征 ---
F = rng.normal(0, 1, (N, 3))
ret = 0.5 * F[:, 0] - 0.35 * F[:, 1] + 0.25 * F[:, 2] + rng.normal(0, 0.4, N)
X = F @ rng.normal(0, 1, (20, 3)).T + rng.normal(0, 1.2, (N, 20)) # 前 8 维含信息
X[:, 8:] = rng.normal(0, 1.0, (N, 12)) # 后 12 维纯噪声(陷阱)
mu, sd = X.mean(0), X.std(0) + 1e-8
Xs = (X - mu) / sd # 标准化(AE 对尺度极敏感)
# --- 参数初始化 ---
p = {"W1": rng.normal(0, .3, (12, d)), "b1": np.zeros(12),
"W2": rng.normal(0, .3, (m, 12)), "b2": np.zeros(m),
"W3": rng.normal(0, .3, (d, m)), "b3": np.zeros(d)}
def forward(Xb):
a1 = np.tanh(p["W1"] @ Xb.T + p["b1"][:, None])
a2 = np.tanh(p["W2"] @ a1 + p["b2"][:, None]) # 瓶颈编码 z (m, b)
return a1, a2, np.tanh(p["W3"] @ a2 + p["b3"][:, None])
def backward(Xb, a1, a2, out):
b = Xb.shape[0]
g = 2 * (out - Xb.T) / b
gW3, gb3 = g @ a2.T, g.sum(1)
da2 = (p["W3"].T @ g) * (1 - a2**2)
gW2, gb2 = da2 @ a1.T, da2.sum(1)
da1 = (p["W2"].T @ da2) * (1 - a1**2)
gW1, gb1 = da1 @ Xb, da1.sum(1)
return {"W3": gW3, "b3": gb3, "W2": gW2, "b2": gb2, "W1": gW1, "b1": gb1}
for ep in range(400): # 小批量 SGD
for s in range(0, N, 64):
Xb = Xs[rng.permutation(N)[s:s+64]]
a1, a2, out = forward(Xb)
g = backward(Xb, a1, a2, out)
for k in g:
p[k] -= 0.02 * g[k]
_, Z, _ = forward(Xs); Z = Z.T # 瓶颈编码 (N, m)python训练曲线显示重建 MSE 从 0.984 降到 0.759(降 22.9%)。注意它没降到 0——因为有 12 维纯噪声根本没法重建,网络把容量都押在了有信息的维度上,这正是我们想要的。

三、把「编码」当新因子:怎么检验它有用#
AE 训练完,每只股票得到 5 维编码 。把它当因子有两用:(1) 对 的某一维做横截面排序(哪维预测收益,看 IC);(2) 把 5 维 当作「提纯后的特征」送进 LightGBM / 线性模型,替代原始 20 维。
我们用 IC(秩相关)回答「编码里有没有收益信号」:
def ic(col):
r = ret - ret.mean(); c = col - col.mean()
return np.corrcoef(c, r)[0, 1]
coef, *_ = np.linalg.lstsq(Z, ret - ret.mean(), rcond=None)
ae_factor = Z @ coef # 用瓶颈 5 维对收益做最小二乘投影
print(abs(ic(ae_factor))) # 组合因子 IC ≈ 0.575python结果:AE 组合因子 ,碾压原始噪声特征的 0.031,也高于原始信息特征的 0.333。把瓶颈编码按未来收益五分位着色,高/低收益簇明显分离:


四、为什么它比「堆更多原始特征」好#
- 降维去噪:20 维里 12 维是纯噪声,原始线性模型会被稀释;AE 把容量集中到 5 维,等于自动做了特征选择。
- 非线性:PCA 只能抓线性共性,AE 的 能抓「估值低 + 波动高」这类交互(核 PCA 才有的能力)。
- 无监督可扩展:不用等下个月收益标签,盘中就能用当日量价更新编码,适合高频迭代。
五、A股 / 实务落地点#
- 防过拟合三件套:权重衰减(L2)、早停(early stopping)、Dropout。AE 容量大,不做正则 = 记住噪声。
- 严防前视泄露:标准化用的 mu/sd 必须来自训练窗口,不能在全样本上 fit 再回放——否则编码里混进未来信息。
- 可解释性补丁:AE 因子是黑箱,监管/归因困难。用 SHAP 回看「哪个原始特征对编码贡献大」,把黑箱因子翻译成人话。
真实陷阱(必须说清楚)#
- 无监督 ≠ 可预测:AE 优化的是「重建」,不是「预测收益」。它学到的可能是「行业共性」「市值规模」这类与收益弱相关的结构。IC 不高时别硬用。
- 容量有限,特征太多反被记:若原始特征里噪声维度远超信息维度且不加正则,AE 会直接记住噪声 → 样本内重建完美、样本外因子失效。
- 分布漂移:训练集是牛市的量价结构,危机期相关性重构,编码瞬间「失真」。要滚动重训 + 监控编码分布(如用 MMD 检测漂移)。
- 可解释性黑洞:瓶颈向量没人看得懂,出问题难定位。务必配 SHAP / 敏感性分析。
- 对离群值敏感:和 PCA 一样,标准化前不 winsorize,几个极端值会主导第一主成分,编码被 outlier 绑架。
收尾:自编码器不是「替代多因子模型」,而是给多因子模型当特征工厂——把脏乱的原始量价,炼成低维、低相关、信息浓缩的新因子。在因子拥挤、线性 alpha 枯竭的当下,这种「无监督提纯」思路,值得每个量化研究员放进工具箱。