从生成式AI工具问世的第一天起,我就一直在用它们编写代码。
如今,它们为我编写了大量的代码。
但除了代码,它们也会制造bug。
不是语法错误、缺少引用之类的问题。
不,是真正的行为性bug。
代码里有些东西错了,需要人为干预。
每当我指出这一点时,总会有人说: “哦,别用那个模型,试试这个模型。”
然后我去用这个模型,结果那个模型又制造了bug。
他们说:“哦,等等新版本,夏天就会发布,它会修复一切。”
夏天来了,bug依然存在。
我知道这是为什么。
故事其实很简单。
Bug实际上是内建在每一个LLM中的。
它们永远不会停止。
但如果你想理解这一点,就需要多花点功夫,而这正是我将在本文中向你展示的。
深入核心:任何LLM的内部构造
我将向你展示LLM的内部构造,任何现存的LLM。
我还会揭示它们所依赖的数学基础,正是这些基础使得bug的出现不可避免。
换句话说,LLM无论训练得多么好,总会制造bug。
这是无法避免的。
我们将移除所有附加在LLM外部的优化和工具,审视其裸模型。
当你剥离聊天界面、IDE集成、代理、插件等一切外壳后,你会发现裸模型。
在每个LLM模型的核心,都存在一个深度神经网络。
一个或多个。
深度神经网络在拓扑上等同于多层感知器,而多层感知器是历史上最古老的人工神经网络拓扑结构。
graph TD;
A[输入层] --> B{隐藏层 1};
B --> C{...};
C --> D{隐藏层 N};
D --> E[输出层];
subgraph "单个神经元"
direction LR
I1[输入 1] --> N;
I2[输入 2] --> N;
Idot[...] --> N;
N((Σ)) -- 非线性激活函数 --> O[输出];
end
style A fill:#cde,stroke:#333,stroke-width:2px
style E fill:#cde,stroke:#333,stroke-width:2px
style B fill:#f9f,stroke:#333,stroke-width:2px
style D fill:#f9f,stroke:#333,stroke-width:2px
一个多层感知器由多个层组成,每个层又由多个神经元组成。
每个神经元有多个输入和一个输出。
第一层只接收输入,不做任何处理。
转换发生在每个神经元中。
每个神经元接收其输入,将它们乘以权重,求和,然后通过非线性激活函数传递。
对一个层中的所有神经元都这样做,你就得到了该层的输出。
然后将其传递到下一层,再下一层,直到你到达输出层,就得到了整个人工神经网络的输出。
那些你用来乘以数值的权重,就是人工神经网络的参数,你也会把它们看作是LLM的参数。
历史的足迹
这里的历史非常有趣。
它始于1943年,第一个人工神经元的解释。
然后在1958年,第一个单层感知器被设计出来。
接着,多层感知器随之而来,我们今天称为深度神经网络的多层感知器也在1960年代被构建出来。
所以,深度神经网络的历史并不新鲜。
它已经有半个多世纪的历史了。
训练与运行:天壤之别
但在一切开始之前,我必须澄清一个非常重要的细节。
训练神经网络和运行神经网络之间存在区别。
这两者是完全不同的活动。
监督学习,这是我们训练人工神经网络(如LLM中的那些)的方式。
网络在其输入端看到大量数据,对于每一行输入,都有一个它应该产生的已知输出。
在训练期间,我们调整网络中的权重,以减少网络实际输出与期望输出之间的差异。
这就是训练。
它可能需要很长时间和非常复杂的算法。
但一旦你完成了训练,你就得到了你的模型,仅此而已。
一旦你开始使用模型,训练就不再对模型产生任何影响。
这样一来,一层神秘的面纱就消失了。
人工神经网络做什么?
它接收输入,通过各层向前传播,产生输出,就是这样。
你得到你的输出。
这其中并没有什么特别之处。
想要输出下一个token?
只需传递新的输入,等待网络的新输出。
仅此而已。
网络可能拥有的任何知识,都固化在深度神经网络内部神经元的权重中。
就是这样。
一个更好、改进的训练算法可以用更少的计算能力,更便宜地生产出新模型,即模型的下一个版本。
这非常好。
它也可能有助于制造出错误更少的模型。
嗯,这很有趣。
我们能制造出一个产生更少错误的新的人工神经网络吗?
这可能吗?
我们能将错误减少多少?
数学真相:通用近似定理
嗯,这就是著名的通用近似定理登场的时候,我们终于进入了深度神经网络的数学背景。
深度神经网络中神经元的非线性变换不是任意的。
它们使整个人工神经网络成为其输入的非线性变换,这非常重要,因为这类函数具有一些可证明的属性。
而通用近似定理指出了其中一些对于人工神经网络理论非常重要的属性。
[!TIP] 该定理首次出现于1989年,当时它被证明,并且它并非凭空出现。它的根源可以追溯到19世纪的数学理论。第一次这样的尝试是1885年的维尔斯特拉斯定理,期间还有其他一些定理,最终为通用近似定理铺平了道路。
这就是该定理所说的:
一个人工神经网络可以在有界输入范围内,以任意精度近似任何连续函数。
现在,这是什么意思?
让我们把它付诸实践。
如果你近似口语中单词(token)的概率,你可以训练网络说那种语言。
如果你在编程语言代码中下一个token的概率上训练它,它就会用那种编程语言编写代码。
这就是人工神经网络所做的。
它们近似你在输入端展示给它的任何东西,然后在之后的实际使用中,尝试产生与训练期间看到的相似或相同的东西。
无法避免的后果
你现在能看到了吗?
人工神经网络学习一种语言的统计模式,然后在被使用时再现这些模式。
这就是它们所做的。
但是,随之而来的是后果。
-
永远不会完美 人工神经网络的产出永远不会没有错误。虽然它可以以任意精度近似一个函数,但它永远不是完美的。总会有错误存在。
-
无法超越训练数据 人工神经网络产生的输出总是类似于它在训练期间看到的输出。它无法处理在训练期间没有见过的东西。你不能向人工神经网络展示一本书,让它以某种方式阅读,然后根据它从那本书中学到的原则来编写软件。那不是人工智能,那是科幻小说。那不存在。
-
边界之外,错误激增 网络只在它被训练的有界输入上,具有我们在训练期间看到的那个错误水平。一旦超出那个输入范围,网络就会开始犯下严重错误。给它看它从未见过的单词,给它看模型训练后才出现的编程语言语法,或者给它看一个它在训练期间从未见过的库,模型就会开始大肆幻觉。
这就是它们的工作方式。
这是内建在它们之中的。
LLM究竟是如何编写代码的?
那么,一个大型语言模型是如何编写代码的呢?
本质上非常简单。
它获取它已经看到的代码。
它获取你的提示。
然后尝试预测下一个token。
然后是下一个,再下一个,再下一个。
所以它编写了一整行或一个函数或一个代码块,无论你希望它写什么。
但是,你必须理解这种行为的某些方面。
你在提示中传递的代码在训练期间从未被见过。
人工神经网络将始终在插值模式下运行。
它将总是试图识别它在训练期间看到的别人代码的片段,然后产生你代码的一个片段。
因此,输出永远不会是精确的。
然后,真相大白。
那段代码是正确的吗?
那段代码是不正确的吗?
模型无法判断。
它需要一个人类在环,来审视代码并判断什么是好的,什么是坏的。
如果人类没有注意到问题,那么bug就会留在那里。
何时会出现Bug?
通用近似定理也告诉我们何时可以预期bug的出现。
在输入的有界区域内,稳定的错误率是预期的。
那是网络被训练的内容。
但即使在那里,情况也不是统一的。
在某些区域,存在数百万个网络训练时所用的样本。
那正是LLM会感到自信的地方。
它会为你编写完美的代码,因为它有无数其他可以插值的样本,从而产生无瑕疵的代码。
但如果你正在从事任何实验性的工作,关于高级算法,任何此类事情,那么训练集中的真实样本就很少了,错误率自然会上升。
你不能指望一个LLM能自信地在你的代码中产生任何奇特的东西。
它会制造非常非常多的bug。
当然,如果你只是走出网络被训练的那个区域,你可以预料到错误率会疯狂飙升。
它可能会制造比解决的问题更多的问题。
结论:Bug将永远存在
这次分析传达的信息是明确的。
LLM永远不会停止制造bug。
无论底层的模型有多大,无论其架构有多好,或者应用了多么先进的训练算法,你今天所看到的本质上就是天花板。
这就是LLM。
LLM不会再有大的改进了。
这是我的预测。
它们会变得更快。
它们会变得更方便,是的。
但它们永远不会变得绝对正确。
它们会以一定的速率不断增加bug,而你将永远需要在那里阅读所有那些代码,并找到和纠正这些bug。