本文中 Andrej Karpathy 提出了一个革命性的观点:神经网络不仅仅是机器学习工具箱中的另一个工具,而是软件开发方式的根本性转变,即从传统的“Software 1.0”过渡到“Software 2.0”。Software 1.0 依赖于人类编写的明确指令,而 Software 2.0 则基于数据集和神经网络架构,通过训练来生成程序。Karpathy 认为这种转变不仅提高了软件性能,还改变了编程范式,使得数据集的管理和优化成为软件开发的核心。

➡️ Software 1.0 vs. Software 2.0

  • Software 1.0: 由人类程序员使用编程语言(如 Python、C++)编写明确的指令,逐行代码确定程序行为。
  • Software 2.0: 使用神经网络的权重表示程序,由数据集和神经网络架构定义,训练过程将数据集“编译”成最终的神经网络。

➡️ 转变实例

  • 视觉识别: 从工程化特征和机器学习模型(如 SVM)转变为使用大规模数据集和卷积神经网络(CNN)。
  • 语音识别和合成: 从预处理和传统模型(如高斯混合模型和隐马尔可夫模型)转变为完全依赖神经网络(如 WaveNet)。
  • 机器翻译: 从基于短语的统计技术转变为神经网络模型,特别是在多语言和弱监督环境中。
  • 游戏: 从手工编码的程序(如围棋程序)转变为神经网络模型(如 AlphaGo Zero)。
  • 数据库: 使用神经网络替代传统数据管理系统组件,提高速度和节省内存。

➡️ Software 2.0 的优势

  • 计算同质性: 神经网络主要由矩阵乘法和 ReLU 组成,简化了实现和优化。
  • 易于硬件实现: 简单的指令集使得神经网络更容易在定制 ASIC 和神经形态芯片上实现。
  • 恒定运行时间和内存使用: 每次前向传递所需的 FLOPS 和内存使用量是恒定的。
  • 高度可移植: 矩阵乘法序列比传统二进制文件或脚本更容易在不同计算配置上运行。
  • 灵活性: 可以通过调整网络结构和重新训练来快速适应新的性能需求。
  • 模块融合: 可以通过反向传播优化相互交互的模块,提升整体性能。

➡️ Software 2.0 的局限性

  • 可解释性差: 大型神经网络的工作原理难以理解。
  • 潜在的失败模式: 可能出现非直观和尴尬的错误,或“静默失败”,如训练数据中的偏见。
  • 对抗样本和攻击: 反映了这种技术堆栈的非直观特性。

➡️ 编程范式的变化

  • 软件开发工具: 需要新的 IDE 和工具来帮助管理、可视化、清理和标注数据集。
  • 代码管理平台: 类似于 Github 的平台,但用于数据集和标签的管理。
  • 包管理和部署: 需要新的工具来有效地部署和共享神经网络模型。

➡️ 未来展望

  • 短期: Software 2.0 将在任何可以重复评估且算法设计困难的领域变得越来越普遍。
  • 长期: 随着 AGI 的发展,Software 2.0 将成为核心编程范式。

Software 2.0

有时我听到人们把神经网络称为“机器学习工具箱中的另一个工具”。它们有其优缺点,适用于特定场景,有时还能在 Kaggle 比赛中取胜。然而,这种看法忽略了神经网络的真正意义。神经网络不仅是另一种分类器,它们代表了一种我们开发软件方式的根本性转变,即软件 2.0。

我们熟悉的“经典栈”中的软件 1.0 是用 Python、C++ 等语言编写的。它由程序员编写的明确指令组成。程序员通过编写每一行代码,确定了程序空间中的一个特定点,使其具有某些理想的行为。

相比之下,软件 2.0 使用的是更抽象、不适合人类编写的语言,如神经网络的权重。这种代码没有人类直接编写,因为权重数量庞大(典型网络可能有数百万个),直接用权重编写代码非常困难(我试过)。

我们的方法是设定程序行为的目标(例如“满足输入输出对的数据集”或“赢得围棋比赛”),然后编写代码的粗略框架(即神经网络架构),确定一个程序空间的子集进行搜索,并利用计算资源在这个空间中找到一个可行的程序。对于神经网络,我们将搜索限制在程序空间的连续子集中,通过反向传播和随机梯度下降使搜索过程变得高效。

为了更明确的类比,在软件 1.0 中,人类设计的源代码(如 .cpp 文件)被编译成执行有用工作的二进制文件。而在软件 2.0 中,源代码通常包括1)定义理想行为的数据集和2)提供代码粗略框架的神经网络架构,许多细节(权重)需要填写。训练神经网络的过程将数据集编译成最终的神经网络二进制文件。在今天的大多数实际应用中,神经网络架构和训练系统越来越标准化,因此大多数的软件开发活动变成了策划、扩展、处理和清理标记数据集。这从根本上改变了我们迭代软件的编程模式,因为团队分为两类:2.0 程序员(数据标注者)编辑和扩展数据集,而少数 1.0 程序员维护和迭代训练代码基础设施、分析工具、可视化和标注界面。

事实上,很多现实世界的问题具有这样的特点:收集数据(或更普遍地说,确定理想行为)比显式编写程序要容易得多。由于这一点以及我将在下文中提到的许多其他软件 2.0 的优点,我们正在见证整个行业的大规模转型,许多软件 1.0 代码正在被转移到软件 2.0 中。软件 1.0 正在吞噬世界,而现在 AI(软件 2.0)正在吞噬软件。

正在进行的转型

让我们简要地看一些正在进行的转型的具体例子。在每一个领域中,当我们放弃通过显式编写代码来解决复杂问题,而转向 2.0 栈时,我们都看到了近几年的显著改进。

视觉识别:过去是工程化特征加上少量机器学习(如 SVM)。现在,通过获取大型数据集(如 ImageNet)并在卷积神经网络架构空间中搜索,我们发现了更强大的视觉特征。最近,我们甚至不再手动编写架构,而是通过自动搜索。

语音识别:过去涉及大量预处理、高斯混合模型和隐马尔可夫模型,但现在几乎完全由神经网络组成。Fred Jelinek 在 1985 年的一句名言常被引用:“每次我解雇一个语言学家,我们的语音识别系统的性能就会提高。”

语音合成:历史上使用各种拼接机制,而今天最先进的模型是大规模卷积网络(如 WaveNet),可以生成原始音频信号。

机器翻译:通常采用基于短语的统计技术,但神经网络正在迅速成为主导。我最喜欢的架构是在多语言环境中训练的,其中一个模型可以从任何源语言翻译到任何目标语言,并且在弱监督(或完全无监督)的环境中表现出色。

游戏:手工编写的围棋程序已经开发了很长时间,但 AlphaGo Zero(一个查看棋盘原始状态并下棋的卷积网络)现在已经成为围棋中最强的玩家。我预计我们将在其他领域看到类似的结果,例如 DOTA 2 或星际争霸。

数据库:传统系统也开始看到转型的早期迹象。例如,“学习索引结构的案例”用神经网络替换数据管理系统的核心组件,速度比缓存优化的 B 树快 70%,同时节省了一个数量级的内存。

上面提到的很多工作都来自 Google。这是因为 Google 目前处于将大部分自身重写为软件 2.0 代码的前沿。“一个模型统治所有”提供了一个早期的概述,其中各个领域的统计强度融合成对世界的一致理解。

软件 2.0 的好处

为什么要将复杂程序移植到软件 2.0?显然,一个简单的答案是它们在实践中效果更好。然而,还有许多其他原因让我们选择这种栈。让我们来看看软件 2.0(例如卷积网络)相比于软件 1.0(例如生产级 C++ 代码库)的好处:

计算上同质:一个典型的神经网络主要由两种操作组成:矩阵乘法和零门限(ReLU)。相比之下,经典软件的指令集要复杂得多。由于只需要为少数核心计算原语(如矩阵乘法)提供软件 1.0 实现,因此更容易做出正确性和性能保证。

容易实现硬件加速:由于神经网络的指令集相对较小,更容易在硬件中实现,如定制 ASIC 和神经形态芯片。当低功耗智能变得普遍时,世界将发生变化。例如,小型、廉价的芯片可能带有预训练的卷积网络、语音识别器和 WaveNet 语音合成网络,所有这些都集成在一个可以连接到任何设备的小型原脑中。

恒定运行时间:每次神经网络前向传播的迭代所需的浮点运算次数(FLOPS)是固定的。相比之下,C++ 代码库的不同执行路径会带来时间上的差异。尽管存在动态计算图,但其执行流程仍然受到显著限制。因此,我们几乎可以保证不会陷入意外的无限循环。

恒定内存使用:与上述相关,没有动态分配的内存,因此几乎不可能发生磁盘交换或内存泄漏。

高度便携:矩阵乘法序列比经典的二进制文件或脚本更容易在不同的计算配置上运行。

非常灵活:如果你有一段 C++ 代码,有人要求你将其速度提高一倍(即使以性能为代价),那么调整系统以满足新要求是非常复杂的。然而,在软件 2.0 中,我们可以拿网络,移除一半的通道,重新训练,速度就会提高一倍,但效果会有所下降。这简直像魔法一样。相反,如果你获得更多的数据或计算资源,只需增加更多的通道并重新训练,程序的效果就会更好。

模块间的优化:我们的软件通常分解为通过公共函数、API 或端点进行通信的模块。然而,如果两个最初分别训练的 2.0 模块交互,我们可以轻松地通过整个系统进行反向传播。想象一下,如果你的网页浏览器可以自动优化底层系统指令,提高网页加载效率。或者你导入的计算机视觉库(如 OpenCV)可以根据你的特定数据进行自动调优。在 2.0 中,这是默认行为。

性能更佳:最后,也是最重要的一点,神经网络在很多有价值的领域,如图像/视频和声音/语音,比人类编写的代码表现更好。

软件 2.0 的局限性

2.0 栈也有一些自己的缺点。优化结束后,我们得到的大型网络虽然表现很好,但很难解释其工作原理。在很多应用领域中,我们面临着使用一个理解的 90% 准确模型,还是使用一个不理解的 99% 准确模型的选择。

2.0 栈可能会以意想不到和令人尴尬的方式失败,或者更糟糕的是,它们可能会“静默失败”,例如在训练数据中静默地引入偏差,这在规模达到数百万时非常难以分析和检查。

我们还在发现这个栈的一些奇特性质。例如,对抗性示例和攻击的存在,突显了这个栈的直观性问题。

在 2.0 栈中编程

软件 1.0 是我们编写的代码。软件 2.0 是基于评估标准(如“正确分类训练数据”)生成的代码。在任何程序不是显而易见但可以反复评估其性能的情况下(如正确分类图像?赢得围棋比赛?),这种转变是可能的,因为优化可以找到比人类编写的更好的代码。

我们观察趋势的视角非常重要。如果你将软件 2.0 视为一种新兴的编程范式,而不是仅仅将神经网络视为一种机器学习技术中的分类器,那么可以预见,未来还有更多的工作要做。

我们已经建立了大量工具,帮助人类编写 1.0 代码,例如具有语法高亮、调试器、分析器、转到定义、Git 集成等功能的强大 IDE。而在 2.0 栈中,编程是通过累积、处理和清理数据集完成的。例如,当网络在某些困难或罕见情况下失败时,我们不是通过编写代码来修复,而是通过增加更多这些情况的标记示例。谁将开发第一个软件 2.0 IDE,帮助完成累积、可视化、清理、标注和获取数据集的所有工作流?也许 IDE 会显示网络怀疑被错误标记的图像,基于每个示例的损失,或通过预测为标注提供标签,或基于网络预测的不确定性建议有用的示例来标注。

同样,Github 是软件 1.0 代码的一个非常成功的家。是否有软件 2.0 Github 的空间?在这种情况下,存储库是数据集,提交由标签的添加和编辑组成。

传统的软件包管理器和相关的服务基础设施(如 pip、conda、docker 等)帮助我们更容易地部署和组合二进制文件。我们如何有效地部署、共享、导入和处理软件 2.0 二进制文件?神经网络的 conda 等价物是什么?

短期内,软件 2.0 将在任何可以反复评估且成本低廉的领域中变得越来越普遍,并且算法本身难以显式设计。在长远来看,这种编程范式的前景是光明的,因为越来越明显的是,当我们开发通用人工智能 (AGI) 时,它肯定会用软件 2.0 编写。