把简单问题写复杂,是一种学术病

摄影:产品经理

左庭右院蔬菜自助

最近我在看一篇关于电子表格理解的论文。

论文里有一个算法,核心流程其实非常简单:先让一个 Agent 从电子表格中抽取结构,再让视觉 Agent 和 LaTeX Agent 分别验证;如果两边都通过,就返回结果;如果没有通过,就把错误反馈回去,重新抽取,直到成功或者达到最大重试次数。

任何一个写过程序的人,看到这里,大概已经知道代码该怎么写了:

1
2
3
4
5
6
7
8
9
10
for _ in range(max_iterations):
result = extractor.run(spreadsheet, feedback)

vision_check = vision_verifier.verify(result)
latex_check = latex_verifier.verify(result)

if vision_check.passed and latex_check.passed:
return result

feedback = vision_check.feedback + latex_check.feedback

事情就是这么简单。

但论文当然不能这么写。

它非要把这段代码重新包装成一种半数学、半代码、半自然语言的东西:

1
2
3
4
C ← {S, Prompt, Tool interfaces}
Y ← C[-1]
vision_pass, Δv ← parse_verification(Cv[-1])
latex_pass, Δl ← parse_verification(Cl[-1])

再配上各种花体字母、上下标、希腊字母、集合符号和箭头,最后排成一张看起来非常“学术”的算法图。

我盯着这张图看了半天,脑子里只有一个问题:

这到底是在帮助读者理解算法,还是在阻止读者理解算法?

简单问题一旦进入论文,就必须先被加密

这类伪代码最让人难受的地方,并不是它真的有多难,而是它明明可以很简单,却偏偏要写得很难。

比如:

1
Δv

它无非就是视觉验证产生的修改意见。

正常程序员会写:

1
vision_feedback

作者却偏要写一个希腊字母 Δ,再加一个下标 v。

读者看到 vision_feedback,根本不需要思考,直接就知道这是什么。

但看到 Δv,你得先在脑子里完成一轮解码:

Δ 是差异,还是梯度,还是增量,还是修正?

v 是 vision,还是 verification,还是 value?

这个变量是一个字符串、一组错误、一个修改集合,还是某种数值?

一个本来不需要解释的东西,被符号压缩之后,反而需要读者额外解释。

再比如:

1
C ← {S, Prompt, Tool interfaces}

看起来很数学,实际上无非是在构造一个上下文:

1
2
3
4
5
context = [
spreadsheet,
prompt,
tool_interfaces,
]

可问题是,数学里的 {} 通常表示集合,而集合没有顺序。后面作者却又写:

1
C[-1]

也就是取最后一个元素。

如果 C 真的是集合,那就不存在所谓的最后一个元素;如果 C 实际上是一个消息列表,就不应该用集合符号。

也就是说,这种写法甚至没有因为数学化而变得更加严谨。

它只是获得了一个更加严谨的外观。

这正是我最反感的地方。

有些论文里的数学符号,不是在表达严格性,而是在表演严格性。

我不是讨厌数学,我是讨厌不必要的数学

每次批评这种现象,总会有人跳出来说:

“你觉得公式难看,是因为你数学基础不好。”

这句话当然很方便,因为它可以直接把表达者的问题,变成读者的能力问题。

但我并不反对数学公式。

很多问题离开数学符号,确实很难表达。

比如概率分布、损失函数、梯度、矩阵变换、优化目标、复杂度分析,这些东西如果全部改写成自然语言或者超长变量名,只会更加混乱。

数学符号最大的价值,就是压缩。

一行公式可以描述一整套稳定的数量关系,而且可以方便地进行推导、代换和证明。在这些场景里,符号不但必要,而且优美。

问题在于,并不是所有东西都值得被压缩成数学符号。

一个算法中“先调用 A,再调用 B,如果都成功就返回,否则重试”,本质上是控制流。

控制流最适合用代码表达。

一个系统中“数据从哪里来,经过哪些模块,最后流向哪里”,本质上是架构关系。

架构关系最适合用图表达。

一个方法为什么这样设计、解决了什么问题,本质上是概念解释。

概念解释最适合用自然语言表达。

但现在很多技术论文有一种奇怪的冲动:无论内容本来属于什么表达形式,都要尽可能往公式里塞。

能用一个正常变量名写清楚的,改成希腊字母。

能用一行 Python 写清楚的,改成 LaTeX 伪代码。

能用一段自然语言说清楚的,定义三个集合、两个映射和四个下标。

最后原本很直观的问题,被写成了一份密码本。

看西瓜书的时候,我也有同样的感觉

这种不适感,让我想起以前看周志华的《机器学习》。

这本书非常有名,因为封面上有很多西瓜,大家通常叫它“西瓜书”。

我相信它在机器学习教育史上有它的地位,也相信很多数学基础很好的人,确实能从中获得系统性的知识。

但我第一次翻开它的时候,最直接的感受就是:

满眼都是公式,一行代码都没有。

一个算法究竟接收什么输入,中间数据怎样变化,循环在哪里,分支在哪里,最后返回什么,书里经常不是先通过流程把它讲清楚,而是直接定义一堆符号,然后开始推导。

比如一个决策树为什么要选择某个特征,程序员真正想看的可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
def choose_best_feature(dataset, features):
best_feature = None
best_gain = float("-inf")

for feature in features:
groups = split_dataset(dataset, feature)
gain = calculate_information_gain(dataset, groups)

if gain > best_gain:
best_gain = gain
best_feature = feature

return best_feature

看完这段代码,即使读者还不知道信息增益是怎么算的,也已经明白算法在做什么:

它遍历每一个特征,用这个特征切分数据,计算切分后不确定性下降了多少,然后选择效果最好的那个。

接下来再解释熵,再解释信息增益,再解释公式,读者会知道每一个符号对应程序中的哪一步。

但很多数学教材的顺序正好相反。

它们先告诉你:

接着再告诉你:

然后默认你看到这些符号以后,算法流程就已经自动出现在脑子里了。

对于已经掌握这套知识的人,这样写当然简洁。

但对于正在学习的人,这种表达经常形成一个死循环:

因为不知道算法在干什么,所以看不懂公式为什么这样定义;因为看不懂公式,所以更不知道算法在干什么。

最后读者没有在理解机器学习,而是在逐个解析字符。

大写字母是什么意思,小写字母是什么意思,粗体字母是什么意思,右上角是什么意思,右下角是什么意思,花体字母又是什么意思。

等你好不容易把符号表翻译成人话,早就忘了这一节原本想讲什么。

很多教材把“知识压缩”放在了“知识建立”之前

数学公式本质上是一种高度压缩的信息。

但压缩有一个前提:你得先拥有可以被压缩的东西。

一个熟悉线性回归的人,看到一个损失函数,能够立刻展开出背后的数据、模型、预测误差和优化过程。

因为这些概念已经存在于他的脑子里,公式只是一个索引。

但初学者脑子里还没有这些东西。

你给他一个公式,并不是帮他压缩知识,而是给了他一个压缩包,却没有提供解压软件。

这也是很多所谓“经典教材”的问题。

它们往往是由已经高度熟悉一个领域的人写给另一个熟悉这个领域的人看的,但在出版时却被包装成了初学者教材。

作者自己已经看不见符号门槛了。

他看到的是模型结构,读者看到的是英文字母。

他看到的是概率关系,读者看到的是上下标。

他觉得一个公式非常直观,读者却要花十分钟确认某个 i 到底是样本编号、迭代次数,还是矩阵下标。

于是学习过程变成了符号考古。

更糟糕的是,公式经常成了一种学术装饰

理论工作需要公式,这是毫无疑问的。

但现在很多论文中的公式,已经不只是表达工具,也逐渐变成了一种身份标识。

一个方法如果只用自然语言和代码说明,似乎显得不够高级;给模块起正常名字,似乎显得不够抽象;于是必须定义几个集合,增加几个映射,引入几个希腊字母,再给它们配上上下标。

哪怕公式所描述的内容只是:

“从候选答案中选择得分最高的一个。”

也要写成:

这类公式本身没有错,而且在某些上下文中很合适。

但当整篇论文不断把普通流程翻译成数学符号时,它传达的不再只是信息,还有一种隐含态度:

我的工作足够复杂,所以必须用你看不懂的方式来描述。

符号在这里形成了一道门槛。

越是简单的东西,越需要复杂地写,才能让它看起来像研究成果。

一个普通的循环,如果直接写成 Python,读者一眼就看懂了。一旦读者一眼看懂,就容易发现这个方法可能根本没有那么复杂。

可如果把它写成花体集合、希腊字母、上标、下标和箭头,读者首先感受到的是权威感。

至于内容究竟有没有那么深,反而被掩盖了。

这有点像过去某些人写文章,明明一句“分析用户需求”就能讲清楚,非要写成“基于多维异构信息场的用户意图感知与需求表征”。

不是内容变高级了,只是表达变肿了。

编程语言本身就是一种严格的形式语言

很多人潜意识里觉得,代码不够学术,公式才足够严谨。

这其实是一种非常陈旧的偏见。

现代编程语言本身就是严格的形式系统。

一个变量是什么类型,一个函数接收什么参数,一个循环什么时候停止,一个异常如何处理,一个对象怎样改变状态,代码都可以明确表达。

甚至在描述算法执行过程时,代码往往比论文伪代码更加严谨。

还是以前面那篇论文为例。

原文写:

1
2
until verification succeeds or max-iterations reached
return Y*

但它没有明确说明,如果最大迭代次数到了,验证仍然没有成功,应该怎么办。

而真正的代码必须处理:

1
2
3
4
5
6
for _ in range(max_iterations):
...
if verified:
return result

raise VerificationError("Verification failed")

或者至少返回最后一次结果以及失败原因:

1
2
3
4
5
return {
"success": False,
"result": last_result,
"errors": feedback,
}

代码无法靠排版营造一种“差不多说清楚了”的感觉。

它最终必须执行。

正因为必须执行,很多含糊的地方才会被迫暴露出来。

谁负责判断 Agent 是否继续调用工具?

工具失败以后是否重试?

两个验证器是否并行执行?

反馈冲突时如何处理?

最大重试次数是多少?

最后一次失败结果是否保留?

这些才是真正决定算法行为的问题。

而不是把 vision_feedback 写成 Δv

好的技术表达,应该降低理解成本

我一直认为,技术表达最重要的评价标准,不是看起来多么专业,而是它是否让一个本来复杂的问题变得更容易理解。

好的公式,可以把一页文字压缩成一行关系。

好的代码,可以把抽象算法变成可执行流程。

好的图,可以让模块和数据流一目了然。

好的自然语言,可以解释为什么要这样做,以及这样做解决了什么问题。

它们之间并不存在高低贵贱。

真正成熟的表达,应该根据内容选择工具,而不是无论什么内容,都强迫它穿上一件数学外套。

最理想的技术教材,通常应该按照这样的顺序展开:

先用自然语言建立直觉,让读者知道问题是什么;再用图或者代码展示流程,让读者知道系统怎样运行;最后用数学公式精确定义关键关系,让读者知道为什么成立。

自然语言回答“是什么”。

代码回答“怎么做”。

公式回答“为什么”。

但很多论文和教材把这三件事全部扔给了公式。

然后把读不懂的责任留给读者。

数学不应该成为知识的防盗门

我并不期待所有论文都改成 Python 教程,也不认为机器学习可以完全绕开线性代数、概率论和微积分。

真正想深入理解算法,数学永远绕不过去。

但数学应该是一座桥,而不是一堵墙。

公式应该出现在它真正能够减少歧义、压缩关系、支持推导的地方,而不是用来装饰每一个普通流程。

希腊字母也不是越多越高级。

下标也不是越复杂越严谨。

花体集合更不会自动让一个普通想法变成理论贡献。

当一个公式帮助读者更快地理解问题时,它是工具。

当一个公式只是把正常变量名替换成希腊字母,把简单流程重新编码一遍时,它就是障碍。

当一篇文章必须依靠大量符号,才能维持自身的深奥感时,我们甚至应该反过来怀疑:

它究竟是在压缩复杂思想,还是在掩盖思想并不复杂?

我反感的从来不是数学。

我反感的是有人把数学当成知识的防盗门。

仿佛只有穿过一大片希腊字母,才有资格接触后面的内容;仿佛表达得越难懂,工作就越有价值;仿佛读者花在解码符号上的时间,也能算作作者思想的深度。

真正高级的表达,不是把简单的事情写复杂。

而是把复杂的事情讲简单。

公式应该是思想的压缩器,而不是学术的遮羞布。

END

未闻 Code·知识星球开放啦!

一对一答疑爬虫相关问题

职业生涯咨询

面试经验分享

每周直播分享

……

未闻 Code·知识星球期待与你相见~