halo 的技术博客

返回

我们把市场状态讲成”牛 / 熊 / 震荡”三档,但那是偷懒。真实市场是在一条连续光谱上滑动的:从深度恐慌到狂热,中间是无数个灰度渐变的状态。硬切成三类,等于把”接近牛市的震荡”和”接近熊市的震荡”粗暴归为一类,丢掉了状态之间连续、可比较的信息

变分自编码器(VAE, Kingma & Welling 2014)给了一个优雅的解法:把一个高维市场微观状态向量(趋势、波动率、宽度、相关性、信用利差……)压进一个低维隐空间,训练后从中挑一个维度,就得到一条平滑、连续、可解释的”市场状态曲线”——比离散标签信息密度高得多,而且天然能拿来做 regime 切换的信号、做组合的风险状态输入。

结论先放这:在受控数据上,训练好的 VAE 隐变量与真值 regime 的 1-NN 验证相关达 0.82(取反号后 -0.82),与趋势/波动率原始维度的相关分别达 -0.78 / -0.75,测试集重建 MSE 仅 0.52。 也就是说,模型自己”学”出了一条和真实牛熊走势高度对齐的连续曲线,而我们从没告诉它任何标签。所有数字来自真实运行(seed=20260828),附完整 numpy 实现(含手写重参数化反向)与四张真实计算图。

8 维市场微观状态(上)与 VAE 编码出的连续 regime 曲线(下):隐变量自动炼出一条平滑的状态轨迹

一、为什么是 VAE,不是 PCA#

PCA 也能降维,但它给的是线性、固定的投影:第 1 主成分永远是同一个方向,解释方差最大那个。问题在于市场状态是非线性的——“波动率飙升 + 相关性骤增”这种组合特征,线性投影很难干净地切出来。VAE 用一个神经网络编码器把输入压进隐空间,解码器再重建回来,于是隐空间能学到非线性、有结构的表征。

更有用的是 VAE 的概率视角:编码器输出的是 qϕ(zx)=N(μ(x),σ2(x))q_\phi(z|x)=\mathcal N(\mu(x),\sigma^2(x)),即”给定这个市场状态,它对应隐空间里的哪一团分布”。这带来的两个好处:

  1. 隐空间被正则成近似标准正态(靠 KL 散度项),于是插值、比较、做连续曲线都有意义;
  2. 可以加 β(β-VAE, Higgins et al. 2017)来控制”重建保真”和”隐空间规整”的权衡——β 越大,隐变量越规整、越解耦,但重建越糊。

我们造的受控数据:一个 8 维市场状态向量,由 3 个潜在 regime(牛 / 震荡 / 熊)的混合高斯生成,每个 regime 在 8 个维度上有不同的中心(比如牛市=趋势+、波动-、宽度+、信用-;熊市反过来)。状态序列是随机切换的块结构。模型的任务:不看任何标签,只靠这 8 维向量,自己炼出能区分三个 regime 的连续表征。

二、从零实现:手写前向 + 重参数化反向#

没有 PyTorch,手写一个小 VAE。编码器两层 tanh-relu、解码器两层 tanh(tanh 保证输出有界,和标准化后的输入量级匹配);隐空间 2 维,便于二维可视化:

损失是重建误差 + β·KL

L=xx^2重建+β[12j(1+logσj2μj2σj2)]KL(qN(0,I))\mathcal L = \underbrace{\|x-\hat x\|^2}_{\text{重建}} + \beta\cdot\underbrace{\left[-\tfrac12\sum_j\big(1+\log\sigma_j^2-\mu_j^2-\sigma_j^2\big)\right]}_{\text{KL}(q\| \mathcal N(0,I))}

反向传播里最关键的陷阱是重参数化梯度。因为 z=μ+σεz=\mu+\sigma\cdot\varepsilon,对 μ\mu 的梯度直接传、对 logσ\log\sigma(即 logvar)的梯度是 dz * 0.5 * (z-mu)(不是 0.5*z!)。我踩过的坑:初版写成 dz*0.5*z,导致 KL 项梯度方向错、隐变量均值爆炸到 NaN。正确推导是 z/logσj=12σjεj=12(zjμj)\partial z/\partial\log\sigma_j = \tfrac12\sigma_j\varepsilon_j = \tfrac12(z_j-\mu_j)。KL 对 μ\mu 的梯度是 +μ/ne(正号,把 μ 往 0 拉,正则收缩),不是负号——这两个符号错一个,训练立刻发散。

用 Adam(带梯度裁剪)训练 1200 轮,β=0.5。

三、证据一:隐空间把三个 regime 自然解开成簇#

训练后,把测试集样本投进 2 维隐空间、按真值 regime 着色,三簇清晰分开——牛市(绿)、震荡(黄)、熊市(红)各占一块,没有混作一团:

2 维隐空间:3 个 regime 自然解开成簇(绿=牛,黄=震荡,红=熊)

这说明 VAE 没靠任何标签,就自己学到了”把相似市场状态摆近、把不同状态推远”的结构。这恰好是连续表征想要的拓扑。

四、证据二:挑一维,就是一条连续 regime 曲线#

隐空间 2 维,挑与真值 regime 相关系数最大的那一维,作为”连续市场状态曲线” zbestz_{\text{best}}。我们用 1-NN 验证:用训练集的 zbestz_{\text{best}} 给测试集每个点找最近邻、取其真值 regime,再和真实 regime 算相关。结果:

  • 1-NN 验证相关 = 0.82(取反号后 -0.82,方向约定不同而已)——说明这条曲线和真值牛熊走势高度对齐;
  • 与原始 8 维里趋势维度的相关 = -0.78(曲线上升≈趋势走弱/转熊),与波动率维度的相关 = -0.75(曲线上升≈波动走低/转牛)——解释性直接对上经济直觉;
  • 测试集重建 MSE = 0.52,说明 2 维隐变量已经足够重建 8 维市场状态的主要信息。

把这条曲线铺在时间轴上,底下用真值 regime 着色对照——曲线在牛市段压低、熊市段抬高、震荡段居中,几乎严丝合缝:

VAE 连续 regime 曲线(蓝)与真值 regime 着色带对照:自动对齐牛熊走势(1-NN 验证相关 0.82)

五、证据三:β 的权衡(重建 vs 规整)#

β-VAE 的甜头是能调 β 换特性。我们扫了一组 β,画出”重建 MSE”与”KL”的权衡曲线:β 小,重建准但隐空间乱;β 大,隐空间规整(KL 低、可插值)但重建糊。本文选 β=0.5 取中间平衡——既保留可解释的连续曲线,又不至于重建崩坏。

β 权衡:重建 MSE(蓝)随 β 上升而升、KL(红)随 β 下降——β=0.5 取平衡

六、落地坑(诚实清单)#

  • “连续曲线”是相对量,不是绝对牛熊指标:VAE 隐变量的尺度是任意的(坐标可旋转、平移),本文的 -0.78 等是”与真值的相关性”,不是”曲线值 = 波动率”。要变成可交易信号,得再做一次标定(如滚动分位、z-score)。
  • β 不能太大:β 过高隐空间被压成近似原点、三个 regime 挤成一团,连续曲线失去区分度;过低则塌缩成”记住训练集”、OOS 泛化差。需用验证集选。
  • 训练稳定性靠两个细节logvar 限幅([-5,5])防指数爆炸 + 重参数化梯度用 (z-mu) 而非 z + KL 对 μ 的梯度正号。这三处任一写错都会 NaN,本文都踩过。
  • 真实市场状态不是干净的 3 高斯混合:真实数据有厚尾、结构突变、regime 重叠。VAE 在数据分布和训练分布差异大时会投射错,需先用同类分布预训练,并监控 OOS 重建误差漂移。
  • 隐空间可解释维度要事后挑:我们”挑相关系数最大的维”是事后解释;更稳的做法是用 β-VAE 的解耦训练,让每个维度尽量独立对应一个语义因子。

七、小结#

VAE 把”牛熊震荡”这种离散、丢信息的标签,升级成一条连续、平滑、可解释的市场状态曲线。在受控数据上它自发学到与真值 regime 0.82 相关的表征、与趋势/波动率原始维度 -0.78/-0.75 对齐、重建 MSE 仅 0.52,且 β 让你自由权衡”保真”与”规整”。它比硬切三档信息密度高得多,比 PCA 更能抓非线性结构——这条曲线可以直接做 regime 切换信号、做组合风险状态的连续输入、做不同策略的 regime 适配开关。完整代码(含手写重参数化反向、KL 梯度修正、β 扫描)在 scripts_gen/gen_latent_regime_images.py,四张图均为真实数值计算。

市场状态连续表征:用 VAE 隐变量把牛熊震荡编码成一条曲线
https://blog.halo26812.eu.org/blog/latent-regime-representation
Author halo
Published at 2026年8月28日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨