SAX 符号聚合近似:把连续价格序列离散成可检索的字符串
SAX(Lin-Keogh 2003/2007)把时间序列变成字符串的三步流水线:z-normalize → PAA 分段平均 → 高斯等概率断点离散,128 个点的窗口压成 8 个字符的词。它的杀手锏是 MINDIST 下界性质:词距离永不超过真实欧氏距离——3000 对随机游走实测违反率 0.00%,这让哈希桶预筛选保证零漏检。合成价格实验:5 处植入的 V 形反转模体,SAX 词哈希在 (w=6, a=5) 参数下 F1=1.00 完美检索,全程只有字典查找、无距离计算。参数热力图诚实展示敏感性:F1 从 0.28 到 1.00 剧烈波动,词太短撞词、太长碎片化。三盆冷水:等概率断点的高斯假设被肥尾收益率违反、z-norm 抹掉波动率量纲让盘整与崩盘同词、符号匹配≠预测力。与 Matrix Profile 对比:SAX 是倒排索引式召回,MP 是精确最近邻,实盘管线的正确姿势是 SAX 粗筛 + 欧氏/MP 精排。
一句话版本#
SAX(Symbolic Aggregate approXimation):把一段连续时间序列先 z-normalize,再用 PAA 压成 段均值,最后按标准正态分布的等概率断点把每段映射成一个字母——128 个浮点数变成 ccaaddbb 这样一个 8 字符的词。从此形态检索变成了字符串匹配:哈希、倒排索引、后缀树,整个文本检索工具箱都能砸向价格序列。而且它带一个数学保证:词之间的 MINDIST 距离永远不超过原序列的真实欧氏距离(下界性质),用它做预筛选保证不漏检。
这是上一篇 Matrix Profile 的镜像篇:MP 走的是”精确计算所有距离”的路线,SAX 走的是”先离散再检索”的路线。两者在实盘管线里是上下游关系,不是竞争关系。
为什么要把数字变成字母#
价格序列的形态检索有一个根本矛盾:连续空间里没有”相等”,只有”距离”。你想找”历史上所有和这两周走势相似的时期”,在浮点世界里只能算距离然后排序——每次查询都是全库扫描。
文本世界完全不同:字符串相等是 的哈希判定,前缀匹配有 trie,模糊匹配有编辑距离和 n-gram 索引,几十年的信息检索技术随取随用。
SAX 的提议就是搭一座桥:用一个带失真保证的量化器把序列送进符号世界。三步流水线:
第一步:z-normalize。 窗口内减均值除标准差。这一步统一量纲——50 元的股票和 5000 元的股票、2015 年的高波动和 2017 年的低波动,全部拉到同一个尺度上比形状。
第二步:PAA(Piecewise Aggregate Approximation)。 把长度 的序列切成 等段,每段取均值。 就是 16 倍压缩。PAA 本身就有下界性质(分段均值的距离 ≤ 原始距离,由 Cauchy-Schwarz 直接得),是整个框架的第一层保护。
第三步:等概率断点离散。 关键设计:断点不是均匀切分值域,而是按标准正态分布的等概率分位数切。字母表大小 时断点是 。z-norm 之后的序列近似服从标准正态,等概率断点保证每个字母出现频率近似相等——信息熵最大化,字母不浪费。20 万个标准正态样本实测四个字母的频率:24.9% / 25.1% / 25.0% / 25.0%,误差在千分之一量级。

图中 128 个点的含噪正弦序列,经过 8 段 PAA(红色横线)和 4 字母离散(灰色虚线是断点),变成词 “ccaaddbb”。整个形态——先高、落底、冲顶、回中——被 8 个字符无损地记住了骨架。
import numpy as np
from scipy.stats import norm
def paa(x, w):
n = len(x)
idx = (np.arange(n) * w) // n # 每个点归属哪一段
return np.array([x[idx == i].mean() for i in range(w)])
def sax_word(x, w, a):
z = (x - x.mean()) / (x.std() + 1e-12) # z-normalize
p = paa(z, w) # PAA 压缩
bp = norm.ppf(np.arange(1, a) / a) # 高斯等概率断点
syms = np.digitize(p, bp)
return "".join(chr(ord("a") + s) for s in syms)python核心实现 12 行。这是 SAX 流行 20 年的重要原因之一:没有任何需要调优的数值过程,离散化是确定性的查表。
MINDIST:不漏检的数学保证#
把连续值压成字母当然有信息损失。SAX 的精髓不在压缩本身,而在于对损失方向的控制:定义在词上的 MINDIST 距离
其中 查一张 的表:相邻或相同的字母距离为 0,隔开的字母距离为两者断点之差。可以证明:
词距离是真实距离的下界,永远不高估相似性、只可能低估距离。 3000 对随机游走序列(n=64, w=8, a=6)实测:

3000 个点全部落在对角线下方,违反率 0.00%,平均紧度(MINDIST/真实距离)约 0.50。
这个性质的工程价值叫 no false dismissal:你想找距离查询序列小于 的所有历史窗口,先用 MINDIST 过滤——凡是 的候选必然真实距离也超标,可以安全丢弃;剩下的候选再算真实距离精排。第一阶段是字符串运算(快几个数量级),第二阶段只处理漏斗尖上的少数候选,而且数学上保证没有真答案被第一阶段误杀。假阳性有(紧度 0.5 意味着过滤不算锐利),假阴性零。
这正是数据库索引的设计哲学——GiST 树的 bounding 保证、R 树的 MBR,全是同一个思路。SAX 把它带进了时间序列。
实验:SAX 词哈希检索价格模体#
合成一份 3000 日的价格序列,在 5 个位置(400/900/1500/2100/2700)植入同一个 40 日 V 形反转形态(急跌 12 日 + 反弹 20 日 + 横盘 8 日),每处叠加独立噪声。任务:只用 SAX 词的哈希匹配(不算任何距离)找回这 5 处植入。
做法简单到近乎作弊:对每个 40 日滑动窗口算 SAX 词,扔进 dict[word] -> [位置列表];然后拿第一个植入窗口的词当查询键,直接查字典。
默认参数 (w=8, a=4) 的结果:查询词 caaabddd 命中 4 处(去重后),其中 3 处对上植入点、1 处误报、漏掉 2 处:

上图橙色带是植入位置,红色段是词命中窗口;下图把命中窗口 z-norm 后叠放——同一个 8 字符词背后的形态族肉眼可见地同构:先坠、后爬、再平。
3/5 的召回不算惊艳。但这恰恰是 SAX 最值得诚实展示的一面——性能对 (w, a) 参数敏感。全参数网格扫描的 F1 热力图:

F1 从 0.28(w=4, a=3)到 1.00(w=6, a=5) 剧烈波动。规律清晰:
- 词太粗(w 小、a 小):符号空间只有 个词,背景窗口大量撞词,精确率崩塌;
- 词太细(w 大、a 大):同一形态的 5 个含噪副本被噪声推过断点、散进不同的词,召回率崩塌;
- 甜蜜点在中间:(w=6, a=5) 时符号空间 个词,恰好大到背景不撞词、小到噪声不破坏形态签名,5 处植入一个不漏、零误报。
这就是量化领域用 SAX 的第一课:w 和 a 不是随便抄论文默认值的,它们隐式定义了你对”相似”的容忍度,需要按形态长度、噪声水平做小规模标注验证。经验起点: 取窗口长度的 1/5 到 1/8(每段 5-8 个交易日,对齐周级结构),(Lin-Keogh 原文实证过 超过 6 收益递减)。
SAX vs Matrix Profile:召回与精排#
上一篇的 Matrix Profile 与本篇的 SAX 是形态挖掘的两极:
| SAX 词哈希 | Matrix Profile | |
|---|---|---|
| 范式 | 离散化 + 倒排索引 | 精确最近邻距离 |
| 查询成本 | 字典查找 | (MASS)起步 |
| 结果性质 | 候选集(有假阳性,无假阴性*) | 精确 top-k |
| 参数 | w, a(敏感) | 只有窗口 m |
| 扩展性 | 天然适配十亿级索引(iSAX 树) | 单机 GPU 到千万点级 |
*在 MINDIST 过滤模式下无假阴性;纯词相等匹配模式会有(噪声跨断点)。
实盘管线的正确姿势不是二选一,而是级联:SAX/iSAX 索引在全市场 × 全历史的海量窗口上做粗召回(毫秒级),MINDIST 下界砍掉大头,最后对幸存候选用精确欧氏距离或 MP 精排。这是 Keogh 团队自己给出的架构(iSAX 2.0 索引了十亿级序列),也是”符号世界的检索速度 + 连续世界的度量精度”的标准组合拳。
SAX 的符号化还打开了另一扇门:词之后的一切 NLP 技术。词频统计(哪种形态在牛市高频出现)、n-gram 转移(词 A 后面接词 B 的条件概率)、TF-IDF(哪个形态是某只股票的”特色词汇”)、甚至把 SAX 词序列喂给序列模型。HOT SAX 异常检测(找出现次数最少的词附近的窗口)就是词频思路的直接产物,比 MP 的 Discord 提前了十年。
冷水三盆#
第一盆:等概率断点的高斯假设,被金融数据结构性违反。 断点来自标准正态分位数,而日收益率是肥尾的(超额峰度普遍 3 起步)。z-norm 后的价格窗口(注意:对价格窗口做 z-norm,不是对收益率)在趋势段还会呈双峰分布。后果是字母频率失衡——极端字母 a/d 被肥尾喂得过饱,中间字母信息量下降。修正方案存在(按经验分位数自适应断点,即 aSAX),但代价是丢掉跨序列的词可比性:两只股票的断点不同,同一个词不再意味着同一个形状。用标准 SAX 就要接受轻度频率失衡,用自适应断点就要放弃全局词典——没有两全。
第二盆:z-norm 抹掉幅度,盘整与崩盘可能同词。 这是和 MP 共享的原罪,在 SAX 里更隐蔽:一段 ±0.5% 的碎步阴跌和一段 −15% 的瀑布崩盘,z-norm 之后如果相对形状相同,SAX 词完全相同——字典查找会把它们放进同一个桶里,连 MP 里那种”距离偏大”的残留线索都没有。实操必须把窗口波动率作为独立特征带在词旁边(比如词后缀加一个波动率分桶字符),否则形态检索会在最关键的时刻给你最误导的答案。
第三盆:检索到 ≠ 可预测。 本文实验证明的是”植入的重复形态能被词哈希找回”,只字未提这些形态之后价格往哪走。把”历史上出现过 17 次同词形态”当成交易信号,需要独立回答:这 17 次之后的前向收益分布是否显著异于随机基线?17 个样本的检验功效够不够?形态频率本身是不是波动率 regime 的代理?上一篇 MP 文章的事件研究框架原封不动适用于此。SAX 是显微镜,不是水晶球。
A 股使用注记#
- 窗口跨停牌段直接丢弃,不要插值后再 z-norm——插值段的零方差会扭曲 PAA 均值;
- 一字涨跌停日的价格序列信息含量极低,建议含 ≥2 个一字板的窗口打上特殊标记(它们会聚成高频词,污染词频统计);
- 词典跨市场共享要谨慎:A 股的 ±10% 涨跌幅约束截断了收益分布尾部,与美股窗口的断点语义并不对齐;
- 复权价是唯一正确输入,除权缺口会制造完全虚假的
a→d跳变词。
结语#
SAX 的贡献不是压缩率,是范式搬运:把时间序列从只有距离度量的连续空间,搬进有相等判定、有哈希、有索引、有整套检索理论的符号空间,并且用 MINDIST 下界给这次搬运上了”不漏检”的保险。12 行代码的离散化 + 一张查表距离,换来的是把形态检索从 扫描降到 查找的自由。
实验数字复述一遍作为收尾:等概率断点频率偏差 <0.1%,3000 对序列 MINDIST 违反率 0.00%,最优参数下 5 处植入模体 F1=1.00——以及同样重要的另一半真相:参数不当时 F1=0.28,高斯假设在肥尾面前弯腰,z-norm 让盘整与崩盘同词。先用它加速你已经验证过的形态假设,而不是指望离散化本身产生 alpha。