我翻转了一个符号,却把它当成洞见的那一天
有一次回测返回的胜率,几乎正好与我所期望的相反。我本想让模型大部分时候是对的,可它却大部分时候是错的——而且不是随机地错,而是干净、对称地错,仿佛它一直在努力亏钱。
我没有去问为什么,而是做了一件在大约一个小时里感觉很高明的事。我把逻辑翻转过来去匹配它。我推理道,如果模型可靠地错,那么反着照它说的做就一定可靠地对。一个小小的改动,一条亏钱的规则在纸面上就变成了一条赚钱的规则。我为自己的发现而沾沾自喜。
那并不是一个发现。那是我开始用一次单字符的编辑对自己撒谎的时刻。
那个恰好错了的数字
可疑之处从来都不是结果很糟。糟糕的结果是常态;这份工作大部分时候就是如此。可疑之处在于,它糟得如此整齐、如此镜像对称——这种对称,事后看来,往往意味着底层有某种更平庸的东西坏掉了,而不是一个真正”反着来”的策略。
但当对称指向一个轻松的胜利时,它很有诱惑力。一个乱糟糟的糟糕结果会让你去调试。而一个干净利落地反转过来的结果,则会向你低语一条捷径:你比你以为的更接近了——只要把它掉个头就行。
那个单字符的修复
于是我把它掉了个头。我反转了那个条件——翻转了一个不等号——同样的逻辑,原本一直在亏,现在在回测里赢了。我提交了这个改动,感觉自己智胜了这个问题。整个修正比这句话还要短。
那种”短小”本应是一个警告。真正的优势难以找到,更难以保持。当单个翻转的字符看似把失败变成了成功,诚实的假设不应是你找到了一个隐藏的真理。而应是你找到了一个隐藏的 bug,只不过换了一边站着而已。
为什么翻转符号感觉像天才之举
这套逻辑几乎无法抗拒:如果一个预测器一贯地错,那么把它反转过来就让它一贯地对。一个反向的天才。一座逆向思维的金矿。谁不愿意拿一个可靠地错误的模型,干脆倒过来读呢?
这套推理的缺陷在于”一贯地”这个词。它假定模型确实在预测某种东西——只是方向反了。而这个假定,恰恰是我从未去核实的那一件事。
真正坏掉的是什么
模型并不是反着的。它几乎根本没在预测。它的输出是退化的——被压缩进一个狭窄、毫无信息量的区间里,无论给它看什么,它说的几乎都一样。它并不是学到了一个信号却指错了方向。它是没能学到信号,产生出某种接近噪声、带着一点微弱而毫无意义的偏向的东西。
短短一串大多是反面的抛硬币结果,并不能证明存在一个稳定、可利用的偏差——它同样很可能只是一枚坏掉的或不可靠的硬币。翻转你读取一个含噪信号的方式,并不会让这个信号变得有信息量。它只是给噪声重新贴了个标签。
这次反转让情况更糟了
这是回测最终逼我承认的部分。翻转逻辑之后,性能并没有镜像地向上变好。它依然很差——而且按某些度量来看变得更糟了。根本没有什么可反转的。我拿了噪声,把它翻过来,又得到了噪声,只不过现在它披上了一件”有意图”的戏服。
那个短暂而美丽、让我自觉聪明的结果,正是我以前写过的那种海市蜃楼:一个奉承你的数字,恰好奉承得够久,让你停止去问那个唯一重要的问题——模型到底有没有学到任何东西?
真正的 bug 在我身上
那个单字符的翻转,从来都不只是一次代码改动。它是一个范畴错误。我把一个症状——糟糕的胜率——当成了一个我可以去调的策略,而它其实是一个我拒绝去做的诊断。我没有去问模型的输出为什么会是那个样子,而是把一个不等号对着回测一路优化,直到那个数字变绿。
那就是过拟合最纯粹、最具人性的形态。你不需要一千个参数才能过拟合。你只用一个符号就能做到,只要你愿意根据你想要的结果、而不是你理解的原因来选择它。
一个出人意料的结果是一个问题,不是一个答案
我从中学到的纪律,说起来简单,做起来是真的难:当一个结果出来得与你预期的相反,那是一个问题,不是一个答案。诱惑在于伸手去抓那个能让数字看起来对的最小改动——翻转一个符号,反转一条规则,调换一个比较。那个最小的改动往往是最危险的,因为它修复了表象,却让原因丝毫未动。
如今,当有什么东西在我面前反转时,我会努力抵抗那个优雅的反转,转而去问那个丑陋的问题:不是”我怎样让这个数字指向正确的方向?“,而是”这里到底有没有一个真实的信号,朝着任何方向?“通常还没有。而一个翻转的符号造不出一个来。
一个倒过来读的亏钱模型,仍然是一个亏钱模型。反转唯一改变的,是你要花多久才能发现这一点。
—— 没有信号,没有回报,并非投资建议。