比科卡大学机器学习讲座笔记-全-

比科卡大学机器学习讲座笔记(全)

001:课程概述与基础

在本节课中,我们将学习计算机视觉和深度学习的基础知识。课程将从计算机视觉的定义和历史开始,逐步深入到深度学习的核心组件,包括数据、模型、损失函数和优化器,最后介绍一些现代计算机视觉任务和前沿模型。

什么是计算机视觉?👁️

计算机视觉是从视觉数据中提取有意义信息的过程。视觉数据的形式多样,可以是图像、视频、3D点云或网格,甚至包括医学影像(如MRI扫描)等。

计算机视觉的应用领域 🌍

以下是计算机视觉技术被广泛应用的一些领域:

  • 人脸识别:用于智能手机身份验证等场景。
  • 自动驾驶:用于检测车辆、行人、车道线等。
  • 零售:例如亚马逊无人收银商店,通过视觉算法追踪顾客选取的商品。
  • 医疗健康:辅助进行医学影像(如脑部MRI)的分割与诊断。
  • 制造业与农业:在工厂中进行物体定位,或在农业中检测病虫害或植物营养状况。

计算机视觉简史 📜

计算机视觉的发展历程中有几个关键节点:

  • 第一台数码相机:柯达公司推出的商用数码相机,使得图像能够被计算机处理。
  • 20世纪60年代的神经科学实验:Hubel和Wiesel对猫视觉皮层的研究,揭示了视觉处理的基本原理。
  • “夏季视觉项目”:1966年MIT的项目,被视为计算机视觉作为一个科学领域的开端。
  • 经典算法:1986年Canny边缘检测算法,2001年Viola-Jones人脸检测算法。
  • 数据集挑战赛:2005年PASCAL VOC挑战赛和2010年ImageNet大规模视觉识别挑战赛推动了领域发展。
  • 深度学习的兴起:2012年AlexNet在ImageNet竞赛中取得突破性成绩,标志着深度学习在计算机视觉中占据主导地位。

如今,几乎所有的计算机视觉方法都与深度学习紧密相连,例如卷积神经网络、自编码器、Transformer和扩散模型。从学术论文数量到工业应用(如Google的Astra项目),深度学习的影响力无处不在。

计算机视觉的典型任务 🎯

上一节我们回顾了计算机视觉的发展,本节中我们来看看它具体能解决哪些问题。给定一张图像,计算机视觉可以完成多种任务:

  • 图像分类:判断图像的整体类别(如“胡狼”)。
  • 目标检测与定位:找出图像中有哪些物体(如“胡狼”和“汽车”)并用边界框标出位置。
  • 视觉问答:根据图像内容回答问题(如“这是户外场景吗?”)。
  • 行为识别:识别图像中发生的活动(如“行走”)。
  • 姿态估计:检测图像中人体或动物的关键关节位置。
  • 图像描述:用一句话描述图像内容(如“一只胡狼正在穿过乡村的柏油路”)。
  • 语义分割:为图像中的每一个像素分配一个类别标签(如“道路”、“胡狼”、“植被”),这是一种密集预测任务。
  • 深度估计:预测图像中每个像素距离相机的远近,同样是密集预测任务。
  • 3D形状估计:从2D图像中估计物体的三维形状。
  • 视觉定位:给定一张图像和一张环境地图,确定图像在地图中的对应位置。

从传统方法到深度学习 🤖

传统计算机视觉方法(如基于边缘检测的分类)需要手工设计特征。而在深度学习时代,我们采用数据驱动的方法。其核心流程是:输入数据通过一个由参数定义的模型,得到预测结果。训练的目标是找到一组最优的模型参数,使预测结果与真实标签尽可能接近。这需要通过损失函数来衡量预测与标签的差异,并利用该差异来优化模型参数。接下来,我们将逐一剖析这个流程中的关键组成部分。

数据:模型的燃料 🗃️

在计算机视觉中,输入通常是图像、视频或其他视觉数据。图像在计算机中通常表示为一个整数张量,其形状为 [高度, 宽度, 通道数]。彩色图像有红、绿、蓝三个通道,而灰度图像则只有一个通道。

对于监督学习而言,大规模、带标注的数据集至关重要。ImageNet就是一个被广泛使用的大型标注数据集。

模型:从全连接层到卷积神经网络 🧠

模型通常由多个层堆叠而成。一个“层”本质上可以是一个矩阵乘法运算。以下是几种常见的网络层:

  • 全连接层:由简单的矩阵乘法和偏置项构成。公式为 H = W * X + b。其中 W 是权重矩阵,X 是输入向量,b 是偏置向量。该层将输入的所有元素与所有权重相连。
  • 卷积层:专为处理图像等网格数据设计。它使用一个小的卷积核在输入图像上滑动,进行逐元素相乘后求和的操作。这能有效捕捉图像的局部空间特征。
  • 池化层(如最大池化):对局部区域进行下采样,例如取2x2区域内的最大值。池化层没有可学习的参数,主要用于降低特征图的空间尺寸并增加一定程度的平移不变性。
  • 非线性激活函数:如果只堆叠线性层,网络只能表示线性函数。加入非线性激活函数(如ReLU、Sigmoid、Tanh)能使神经网络逼近任意复杂度的连续函数,这是“通用近似定理”的基础。
  • Softmax函数:在分类任务中,将网络最后一层的输出转换为各类别的概率分布。
  • 正则化:Dropout:为了防止模型在训练集上过拟合,Dropout在训练时随机“丢弃”(即置零)一部分神经元的输出,从而降低模型容量,鼓励学习更鲁棒的特征。在测试时则不使用Dropout。

经典网络架构:AlexNet 与 ResNet 🏗️

结合上述层,可以构建强大的卷积神经网络。AlexNet是2012年ImageNet竞赛的冠军,其结构依次为:输入图像 -> 大卷积层(11x11) -> ReLU -> 池化层 -> 后续卷积与池化 -> 展平 -> 全连接层 -> 输出(1000类)。

AlexNet的成功引发了增加网络深度的趋势。然而,简单地堆叠更多层会导致训练误差和测试误差不降反升,这并非过拟合,而是优化困难。ResNet通过引入“残差连接”解决了这一问题。在一个残差块中,输入 x 会直接跳接到几层之后的输出 F(x) 上,最终输出为 F(x) + x。这种结构使得网络更容易学习恒等映射,让极深的网络(如152层的ResNet)得以成功训练,并在多项任务上取得突破性成果。残差连接也有助于缓解深度网络中的“梯度消失”问题。

损失函数与优化器 ⚖️🔧

对于图像分类任务,我们需要一个损失函数来衡量模型预测的概率分布与真实标签分布的差距。交叉熵损失是常用的分类损失函数。其公式为:
Loss = -Σ (y_true * log(y_pred))
其中 y_true 是真实标签的one-hot编码,y_pred 是模型预测的各类别概率。

得到损失后,我们需要优化器来更新模型参数以最小化损失。随机梯度下降及其变种是常用的优化方法:

  • 批量梯度下降:使用全部数据计算梯度,稳定但计算成本高。
  • 随机梯度下降:每次只用一个样本计算梯度,计算快但噪声大。
  • 小批量随机梯度下降:折中方案,每次使用一小批(mini-batch)数据,在实践中最常用。
    此外,带动量的SGDAdam 等更高级的优化器也广泛使用,它们能加速收敛并提高稳定性。

现代计算机视觉方法掠影 🚀

深度学习的基础已经奠定,现在让我们快速浏览一些解决特定视觉任务的现代方法。

  • 语义分割:Segment Anything Model:这是一个强大的分割模型。它包含一个图像编码器(如基于MAE预训练的ViT)、一个提示编码器(处理用户点击、框选等提示信息)和一个掩码解码器(融合信息并输出分割结果)。它实现了灵活的交互式分割。
  • 视觉问答:PaLI:该模型同时处理图像和文本问题。图像通过视觉Transformer编码,文本通过大型预训练语言模型编码,两者进行交叉注意力计算后,由解码器生成答案。
  • 深度估计:Depth Anything:利用大量无标签数据提升深度估计性能。它使用教师模型为无标签图像生成伪标签,然后让学生模型去学习,并结合有标签数据共同训练。其编码器常基于DINOv2等自监督方法预训练的ViT。
  • 图像生成:unCLIP:属于文生图模型。首先,CLIP模型学习图像和文本的联合嵌入空间。然后,一个先验网络根据文本描述生成图像嵌入。最后,一个扩散模型解码器根据图像嵌入生成高质量图片。
  • 目标跟踪:SiamFC:处理视频中的目标跟踪。它是一个孪生网络,包含两个结构相同的子网络,分别提取模板图像和搜索区域的特征,然后计算其相似度来确定目标位置。
  • 点跟踪:Bootstrapped Trajectory Prediction:这是一种更精细的视频跟踪,预测视频中特定点在所有帧中的轨迹。方法结合了监督学习(在合成数据上训练)和自监督学习(在无标签真实视频上,通过学生-教师模型框架进行一致性训练),从而利用大量真实世界数据提升性能。

总结 📝

本节课我们一起学习了计算机视觉与深度学习的基础知识。我们从计算机视觉的定义、历史和广泛应用开始,介绍了其核心任务。然后,我们深入探讨了深度学习 pipeline 的关键要素:数据表示、模型层(全连接层、卷积层、池化层、激活函数)、经典网络架构(AlexNet, ResNet)、损失函数(交叉熵)以及优化器(SGD)。最后,我们概览了多个现代计算机视觉前沿方向及其代表性模型,包括分割、视觉问答、深度估计、图像生成和目标跟踪等。希望这为你进一步探索机器学习与计算机视觉领域奠定了坚实的基础。

002:深度学习中的优化 🧠

在本节课中,我们将要学习深度学习优化的核心概念。我们将从最基础的梯度下降法开始,探讨其背后的数学原理,并逐步理解动量、Adam等现代优化器的设计思想。我们还将深入分析深度学习优化中一些独特的现象,如隐式正则化、边缘稳定性以及它们如何影响模型的泛化能力。

从连续时间到梯度下降法 📉

上一节我们介绍了优化问题的基本目标:最小化一个关于参数θ的函数E(θ)。本节中我们来看看如何从连续时间的视角推导出梯度下降法。

在连续时间中,如果我们让参数沿着负梯度的方向以无穷小的步长移动,可以保证损失函数单调下降。这个过程可以用一个常微分方程(ODE)来描述:
dθ/dt = -∇E(θ)
对这个方程应用链式法则,可以证明 dE/dt ≤ 0,即损失函数随时间递减。

然而,计算机无法处理连续时间,我们必须进行离散化。通过欧拉积分法对上述ODE进行近似,我们得到了梯度下降法的更新公式:
θ_{t+1} = θ_t - η * ∇E(θ_t)
其中η是学习率。这个推导基于一个关键假设:在一个微小的时间间隔内,梯度是近似恒定的。当这个假设不成立(例如学习率过大)时,离散化误差会导致优化轨迹偏离理想的梯度流,甚至可能发散。

动量与自适应优化器 ⚡

上一节我们介绍了基础的梯度下降法,本节中我们来看看如何通过引入历史梯度信息来加速和稳定优化过程。

动量的核心思想是创建一个梯度的移动平均,让参数更新方向不仅取决于当前梯度,也受到过去梯度的影响。这有助于在梯度方向一致的区域加速,并平滑掉一些噪声。其更新规则通常写为:
v_{t+1} = β * v_t + (1-β) * ∇E(θ_t)
θ_{t+1} = θ_t - η * v_{t+1}
其中β是动量系数,控制历史信息的衰减速度。

从梯度的符号重要性出发,我们可以进一步推导出自适应优化器。一个关键的洞察是:对于参数更新,梯度的方向(符号)比其大小更重要。基于此,我们可以用梯度的符号代替其原始值,这引出了符号梯度下降法。

结合动量的思想(使用移动平均来平滑)和符号的重要性,我们可以自然地推导出RMSProp和Adam优化器。Adam同时维护了梯度的一阶矩(均值,类似动量)和二阶矩(未中心化的方差,用于自适应调整学习率),其更新步骤如下:

以下是Adam优化器的核心更新步骤:

  1. 计算当前梯度 g_t
  2. 更新一阶矩估计:m_t = β1 * m_{t-1} + (1-β1) * g_t
  3. 更新二阶矩估计:v_t = β2 * v_{t-1} + (1-β2) * g_t^2
  4. 对一阶和二阶矩进行偏差校正:m̂_t = m_t / (1-β1^t), v̂_t = v_t / (1-β2^t)
  5. 更新参数:θ_t = θ_{t-1} - η * m̂_t / (√v̂_t + ε)

其中,ε是一个为了数值稳定性而添加的小常数,但它对优化轨迹有不可忽视的影响。

深度学习优化的独特现象 🔬

前面我们讨论的优化器是通用的。本节中我们来看看当这些优化器应用于训练深度神经网络时,会产生哪些独特且有趣的现象。

深度学习优化面临高维、非凸、数据量大等挑战,但其表现往往比理论预期更好。我们关注三个核心问题:

  1. 为什么鞍点不是大问题? 在高维空间中,严格的局部极小点很少,鞍点则很多。理论表明,随机梯度下降(SGD)中的噪声有助于逃离鞍点。
  2. 为什么优化得到的局部极小值能泛化? 优化目标是降低训练损失,但最终我们关心的是测试性能。实验发现,SGD倾向于收敛到“平坦”的极小值,而这些平坦区域通常与更好的泛化能力相关。
  3. 什么是平坦性? 平坦性描述了损失函数在最小值附近的形状。虽然存在重参数化问题(即同一个函数可以对应尖锐或平坦的损失曲面),但优化过程本身可能隐式地偏向于寻找某些类型的解。

隐式正则化与边缘稳定性 🛡️

上一节我们提出了深度学习优化中的几个谜题,本节中我们利用数学工具来深入分析其中一个关键概念:隐式正则化。

我们可以通过“反向误差分析”来建模梯度下降法的离散化误差。分析发现,梯度下降不仅在最小化原始损失函数E(θ),还在隐式地最小化一个修改后的损失函数:
L_mod(θ) = E(θ) + (η/4) * ||∇E(θ)||^2
这个额外的项(η/4) * ||∇E(θ)||^2就是隐式梯度正则化项。它惩罚了梯度范数大的区域,从而倾向于将优化过程推向梯度更小(更平坦)的区域。学习率η越大,这种正则化效应就越强。

对于随机梯度下降(SGD),其隐式正则化效应更强,它倾向于最小化每个小批量上的梯度范数,而不仅仅是全批量上的。实验表明,将SGD的这种隐式正则化项显式地添加到全批量梯度下降的目标函数中,可以显著提升其泛化性能,从而弥合与SGD之间的差距。

另一个重要现象是边缘稳定性。实验观察到,在使用全批量梯度下降训练神经网络时,损失海森矩阵的最大特征值λ_max会增长并振荡在2/η附近。当λ_max > 2/η时,在二次函数中会导致发散,但在神经网络中由于高阶项(非二次项)的隐式正则化作用,优化并未发散,而是出现了损失振荡。这揭示了神经网络损失函数的局部二次近似不足以完全描述其优化动态。

优化在生成模型中的关键作用 🎨

我们学习的优化理论并非纸上谈兵。本节中我们来看看优化技术的进步如何直接推动了生成式人工智能的发展。

在生成对抗网络(GAN)的训练中,优化挑战尤为突出,例如模式崩溃和训练不稳定。以下是一些通过改进优化来提升GAN性能的关键技巧:

以下是提升GAN训练稳定性的优化相关技巧:

  • 增大批次大小:有助于模型在训练中看到数据分布的更多模式,缓解模式崩溃。
  • 使用Adam优化器:相比SGD with Momentum,Adam通常能带来更稳定的训练。
  • 调整Adam超参数:在GAN的双人博弈场景中,使用较低的动量(β1)通常更有效。
  • 谱归一化:虽然看似是架构改动(约束权重矩阵的谱范数),但其本质是改变了优化问题的景观,使训练对超参数更不敏感。

对于扩散模型,其优化目标(预测噪声)比GAN的对抗性目标更简单、更平滑,这本身就是一个巨大的优化优势。当前许多关于扩散模型的研究也聚焦于对其连续时间动力学进行离散化时产生的误差分析。

在大语言模型(LLM)的训练中,稳定性是巨大挑战。例如,注意力层可能发生“崩溃”。使用Adam通常是稳定训练的必要条件。此外,由于模型规模巨大,超参数调优成本高昂。采用“最大更新参数化”等初始化策略,可以使最优学习率等超参数在不同模型宽度下保持相对稳定,从而实现从小规模实验到大规模训练的更好迁移。

总结 📝

本节课中我们一起学习了深度学习优化的核心脉络。我们从梯度下降法的连续时间推导开始,理解了其离散化本质和局限。随后,我们探讨了动量、RMSProp和Adam等自适应优化器的设计直觉与推导。我们深入分析了深度学习优化中的特殊现象,如隐式正则化如何引导优化器找到平坦的极小值并促进泛化,以及边缘稳定性现象所揭示的损失函数特性。最后,我们看到了优化技术的进步在GAN、扩散模型和大语言模型等生成式模型发展中所起到的决定性作用。优化不仅是理论工具,更是推动人工智能前沿发展的实践引擎。

003:Transformer模型 🧠

在本节课中,我们将深入学习Transformer架构。我们将从最基础的细节开始,逐步剖析其核心组件和工作原理,并了解它如何从最初的语言翻译任务扩展到计算机视觉、语音识别乃至强化学习等多个领域。课程最后,我们还将探讨其效率问题及变体模型。


输入与向量化

上一节我们介绍了Transformer的概览,本节中我们来看看模型如何处理输入数据。对于语言任务,输入是文本字符串,但模型内部处理的是向量。

首先,文本需要被转换为模型可处理的向量序列。这个过程分为几个步骤:

以下是文本向量化的主要步骤:

  1. 分词:将输入句子(如 “the detective investigated”)分割成更小的单元,称为“词元”。词元可以是字符、单词,或介于两者之间的子词单元。例如,“investigated” 可能被分割为 “invest”、“igat”、“ed” 三个词元。
  2. 词汇表映射:模型有一个预定义的词汇表,通常包含32,000到256,000个词元。每个词元被映射到词汇表中的索引(一个整数)。常见词通常排在前面。
  3. 词嵌入:模型包含一个可学习的词嵌入矩阵,其维度为 (词汇表大小, 模型维度)。模型维度(如768)是预先设定的。通过这个矩阵,每个词元索引被转换为一个对应的向量。

然而,注意力机制本身不考虑顺序,但语言对顺序敏感。因此,我们需要向词向量中添加位置信息。

位置编码 为序列中每个位置的词向量添加一个独特的向量。一个简化的理解是:它为第一个词的所有向量加上一个固定值(如10),为第二个词加上另一个值(如20),依此类推。这样,模型不仅能通过向量在“空间”中的绝对位置知道它是哪个词,还能通过其相对于该位置“中心”的偏移知道它是哪个具体的词元。实际使用的位置编码比简单的加常数更复杂,但核心思想相同。

至此,我们得到了一个包含语义和位置信息的向量序列,可以输入模型进行处理。


编码器:理解输入

现在我们已经将输入转化为向量,接下来进入编码器部分。编码器的任务是深度理解和处理输入信息。

编码器由多个相同的层堆叠而成,每层包含两个核心子层:多头自注意力和前馈神经网络。

多头自注意力机制

这是Transformer的核心。在这一步,序列中的每个词元(向量)都会“查看”序列中的所有其他词元(包括自己),并根据相关性整合信息。

注意力机制可以看作一种“软”字典查询:

  • 查询:每个输入向量通过不同的可学习线性变换,生成对应的查询向量、键向量和值向量。
  • 计算注意力权重:计算每个查询向量与所有键向量的点积,得到相似度分数。分数经过缩放和Softmax归一化,得到一组和为1的注意力权重。
  • 加权求和:将注意力权重作为系数,对所有的值向量进行加权求和,得到该查询位置新的输出向量。

公式表示如下:
注意力(Q, K, V) = softmax(QK^T / sqrt(d_k)) V

其中,Q是查询矩阵,K是键矩阵,V是值矩阵,d_k是键向量的维度,sqrt(d_k)是用于稳定训练的缩放因子。

多头注意力 是将上述过程并行执行多次(例如16次),每次使用不同的线性变换投影到较低的维度。最后将所有头的输出拼接起来,再经过一次线性变换。这允许模型同时关注来自不同表示子空间的信息。

前馈神经网络

自注意力层之后是一个前馈神经网络。它是一个简单的两层全连接网络,通常中间层的维度是输入维度的4倍,并包含一个非线性激活函数(如ReLU)。

代码描述其结构如下:

FFN(x) = max(0, x * W1 + b1) * W2 + b2
# 其中 W1 的维度为 (模型维度, 4*模型维度), W2 的维度为 (4*模型维度, 模型维度)

每个词元独立地通过这个网络。你可以将其理解为每个词元在收集了上下文信息后,进行“独立思考”和深度处理的地方。研究表明,模型的大部分知识(如事实信息)和参数都存储在这个部分。

残差连接与层归一化

为了确保深度网络能够有效训练,每个子层(自注意力和前馈网络)都包裹着残差连接和层归一化。

  • 残差连接:将子层的输入直接加到其输出上。这极大地改善了梯度流动,使得训练非常深的网络成为可能。一种更直观的理解是:模型维护一个“残差流”,每个子层只是查看这个流并建议一个更新(输出 = 输入 + 子层(输入))。
  • 层归一化:对激活值进行标准化(例如,减去均值,除以标准差),使训练过程更加稳定。关于将其放在残差连接之前(Pre-Norm)还是之后(Post-Norm)存在不同方案,两者在实践中都有应用。

编码器会重复堆叠这种“自注意力 + 前馈网络”的层结构(例如6、12甚至上百次)。最终,编码器输出一个经过深度处理、充分理解了输入信息的向量序列。


解码器:生成输出

上一节我们介绍了编码器如何理解输入,本节中我们来看看解码器如何基于此理解生成输出序列,例如翻译后的句子。

解码器的目标是建模输出序列的概率分布。我们利用概率的链式法则将其分解为一系列条件概率的乘积:
P(输出句子 | 输入) = P(词1 | 输入) * P(词2 | 词1, 输入) * ... * P(词N | 词1...词N-1, 输入)

这样,我们可以通过自回归的方式逐个生成词元:先根据输入预测第一个词,然后将预测出的词作为输入的一部分,再预测第二个词,以此类推。

掩码多头自注意力

解码器的第一层也是自注意力,但它是“掩码”的。因为在生成第t个词时,模型只能看到前面t-1个已经生成的词。训练时,为了效率,我们会一次性输入完整的目标序列,但通过一个掩码矩阵,阻止每个位置关注其后面的位置。

掩码实现:在计算注意力分数矩阵后,将未来位置的分数设置为一个极大的负数(如 -1e9),这样在Softmax之后,这些位置的权重就接近于0。

键值缓存

在推理(生成)时,由于是逐词生成的,每次前向传播似乎都需要为所有已生成的词重新计算键和值向量,这非常低效。键值缓存 是关键优化:在生成第t个词时,将前t-1个词在所有层中计算好的键和值向量缓存起来。生成第t个词时,只需计算当前词的键和值,并与缓存拼接使用。这避免了大量重复计算,但缓存本身会占用大量内存。

交叉注意力

这是连接编码器和解码器的桥梁。在这一层,查询向量来自解码器(当前要预测的词),而键和值向量来自编码器的最终输出。这样,解码器在生成每个词时,都能有选择地聚焦于输入序列中最相关的部分,实现“对齐”。

输出层

解码器同样由多个层堆叠而成(包含掩码自注意力、交叉注意力和前馈网络)。最终,解码器最后一个词元对应的输出向量,通过一个线性层投影到词汇表大小,再经过Softmax,就得到了下一个词的概率分布。


超越文本:Transformer的多模态应用

我们已经详细了解了Transformer在机器翻译中的应用。其成功秘诀在于,它本质上是一个强大的序列建模器。只要能将任何模态的数据转化为向量序列,就能用几乎相同的Transformer架构进行处理。

以下是不同领域的应用方式:

  • 自然语言处理:除了翻译,还演变为三种主要范式:
    • 仅编码器:如BERT,通过掩码语言建模预训练,适用于需要理解文本的任务(如分类、问答)。
    • 仅解码器:如GPT系列,通过自回归语言建模预训练,擅长文本生成。
    • 编码器-解码器:如T5,将多种任务(翻译、摘要、分类)统一为“文本到文本”格式进行训练。
  • 计算机视觉:Vision Transformer将图像切割成固定大小的图像块(如16x16像素),将每个块展平并线性投影为向量,然后加上位置编码,输入标准的Transformer编码器。它在图像分类等任务上取得了超越卷积网络的性能。
  • 语音识别:将音频波形转换为频谱图(一种时间-频率图像),然后像处理图像一样切割成片段并向量化,输入Transformer(通常是编码器-解码器结构)来生成文本。
  • 强化学习:可以将智能体的交互轨迹(观察、回报、动作、奖励)视为一个“句子”。用仅解码器Transformer建模这个序列。在部署时,给定当前观察和一个期望的高回报,模型就能生成像高水平玩家会采取的动作。

混合模态:最强大的应用之一是混合不同模态。可以将图像、文本、音频等任何数据都转化为词元,混合成一个序列,交给一个统一的Transformer模型进行训练和学习。这为实现真正的多模态理解与生成打开了大门。


效率与总结

Transformer常被认为计算昂贵,但原始论文显示,在取得更好效果的同时,其训练成本比之前的序列模型低1-2个数量级。现在的“昂贵”印象源于我们将模型和数据规模推到了前所未有的级别。

关于效率,曾出现许多旨在降低Transformer计算复杂度(尤其是注意力机制的O(n²)成本)的变体模型(如Linformer, Performer)。然而,系统的基准测试表明,在需要长距离建模的任务上,这些“高效”变体在速度提升的同时,性能往往有所下降。原始Transformer架构在效果上仍然难以被纯粹的高效设计所超越。

本节课中我们一起学习了

  1. Transformer如何将文本等输入转化为向量序列。
  2. 编码器通过多头自注意力和前馈网络深度理解输入。
  3. 解码器如何以自回归方式,结合编码器信息,生成输出序列。
  4. Transformer如何通过改变输入表示,成功应用于视觉、语音、强化学习等多个领域,成为通用的序列建模骨干网络。
  5. 其核心的注意力机制、残差连接、层归一化等设计,以及训练和推理中的关键技巧(如掩码注意力、键值缓存)。

Transformer的统一架构打破了不同AI子领域的壁垒,促进了跨领域的知识迁移和技术融合,是当前人工智能发展的基石之一。

004:量子机器学习基础与算法

在本节课中,我们将要学习量子机器学习的基础知识。课程将从量子计算的基本概念讲起,介绍量子比特、叠加态、纠缠等核心概念,然后探讨量子计算如何应用于机器学习领域,包括具有可证明运行时间的算法和变分量子算法。

量子计算简介

量子力学已有近百年历史,其理论框架在1927年的索尔维会议上基本确立。然而,直到20世纪80年代,人们才开始思考利用量子力学系统进行计算。核心思想在于,自然系统能够快速演化量子态,因此我们可以利用它们来模拟其他量子力学对象。

量子计算领域在1995年左右取得了重要进展,出现了两个著名算法:格罗弗算法肖尔算法。格罗弗算法能够以平方根级别的优势加速非结构化搜索。肖尔算法则展示了如何高效进行因式分解和求解离散对数问题,这对许多经典加密原语构成了挑战。

量子力学简化规则

在深入量子机器学习之前,我们需要建立一些基本术语和直觉。

量子比特与叠加态

经典计算的基本单位是比特,其值为0或1。量子计算的基本单位是量子比特。与经典比特不同,一个量子比特可以同时处于0和1的叠加态

从数学上看,一个量子比特是一个二维复向量空间中的向量。我们选择计算基矢,通常表示为 |0⟩ 和 |1⟩。一个通用量子比特的状态可以表示为这两个基矢的线性组合:

|ψ⟩ = α|0⟩ + β|1⟩

其中 α 和 β 是复数,称为振幅。由于测量概率与振幅的平方相关,因此状态向量必须具有单位范数:|α|² + |β|² = 1

当我们测量一个量子比特时,我们只会得到0或1,其概率分别为 |α|² 和 |β|²。测量行为会“坍缩”量子态,使其变为所测得的基态。

多量子比特系统与纠缠

对于n个量子比特的系统,其状态存在于2ⁿ维的希尔伯特空间中。基态是单个量子比特基态的张量积,例如 |00⟩, |01⟩, |10⟩, |11⟩。通用量子态是这些基态的线性组合。

纠缠是量子系统的一种特殊关联。当一个多量子比特系统的状态不能表示为各个量子比特状态的张量积时,我们就说这些量子比特是纠缠的。例如,状态 (|00⟩ + |11⟩)/√2 就是纠缠态。测量其中一个量子比特会立即决定另一个量子比特的状态,无论它们相距多远。

量子演化与测量

量子态通过酉操作进行演化。酉矩阵是保持向量范数的矩阵,可以看作是复向量空间中的旋转。量子算法本质上就是一系列酉操作。

测量是获取量子系统信息的唯一方式,但它具有破坏性且信息不完整。你无法通过单次测量获知完整的量子态振幅。

量子计算的能力与局限

一个常见的误解是量子计算机强大是因为它可以“同时探索所有路径”。虽然量子态确实可以处于所有可能基态的叠加中,但问题在于信息提取。

考虑在一个无序数据库中搜索特定项(“大海捞针”问题)。经典计算机在最坏情况下需要检查所有N个条目。量子计算机可以制备一个包含所有可能输入的均匀叠加态,并通过一次查询将解标记出来。然而,直接测量得到解的概率极低(1/N)。格罗弗算法通过巧妙地应用酉操作,能够将成功概率放大,从而仅需大约 √N 次查询即可找到解,实现了二次加速。

这表明,尽管量子并行性存在,但由于测量的限制,我们无法总是获得指数级加速。此外,量子态不可克隆,每次测量后都需要重新准备状态。

量子计算模型与复杂度

对于计算机科学家而言,我们需要知道如何用物理操作实现这些数学概念。量子门电路模型提供了这样的框架。

以下是量子计算中的基本门:

  • 单量子比特门:如泡利-X门(量子非门)、哈达玛门(H门,用于创建叠加态)。
  • 多量子比特门:如受控非门(CNOT门,用于创建纠缠)。

通过组合一组通用的量子门,我们可以近似实现任何酉操作。量子算法的复杂度通常通过所需量子门的数量电路的深度来衡量。另一种抽象度量是查询复杂度,即调用某个黑盒函数的次数。

量子计算机能取代经典计算机吗?答案是否定的。量子计算机可以模拟任何经典计算机,但并非所有任务都更快。量子操作本身可能更慢,且量子比特容易受到噪声干扰而退相干,需要复杂的纠错机制。量子优势体现在针对特定问题(如因式分解、某些线性代数问题)的渐进性加速上。

量子机器学习算法概览

量子机器学习算法主要分为两大类:

  1. 具有可证明运行时间的算法:从算法角度出发,旨在为机器学习任务提供理论上的渐进性加速保证。这类算法通常基于量子线性代数。
  2. 变分量子算法:一种启发式方法,类似于经典深度学习,使用参数化的量子电路,通过经典优化器调整参数以最小化损失函数。理论保证较少,但更适合近期有噪声的量子设备。

量子机器学习工具箱

要构建量子机器学习算法,我们需要一系列基础模块。

数据编码

将经典数据加载到量子态中是第一步。主要有三种方式:

  • 基态编码:直接将n位经典比特串编码到n个量子比特的基态上。例如,比特串“101”编码为 |101⟩。
  • 振幅编码:将一个长度为N的经典向量 v 的各个分量编码为一个 log₂(N) 个量子比特的态的振幅中:|ψ⟩ = Σᵢ vᵢ |i⟩。这实现了数据的指数压缩。
  • 块编码:将一个矩阵 A 嵌入到一个更大的酉矩阵 U 的左上角块中:U = [[A, ·], [·, ·]]。这允许我们通过调用 U 来间接对 A 进行操作。

量子存储

理想的工具是量子随机存取存储器。QRAM允许以叠加态查询地址,并返回相应数据的纠缠态。其查询深度可以是对数级的,但初始化的成本通常与数据大小成线性关系。QRAM是实现上述编码方式的关键。

量子线性代数例程

基于HHL算法求解线性系统的思想,发展出了一套量子线性代数工具包,包括:

  • 矩阵求逆。
  • 矩阵向量乘法。
  • 奇异值变换:一种强大的框架,可以对嵌入矩阵的奇异值施加任意函数变换(如求逆、阈值化等)。

信息提取

最终我们需要从量子态中读出经典信息。常用技术包括:

  • 振幅估计:以优于经典蒙特卡洛方法的速度估计某个量子振幅的大小(即概率)。
  • 取样:从量子态对应的概率分布中抽取样本。

算法示例:量子特征脸

让我们通过一个简化例子——量子版本的特征脸算法,来感受量子机器学习算法的构造思路。该算法用于人脸识别。

经典算法步骤包括:数据中心化、提取前K个主成分、将数据投影到主成分空间得到权重向量,最后通过比较权重向量的距离进行分类。

量子算法假设数据已通过QRAM以振幅编码形式存储。其核心思想是:

  1. 利用块编码将主成分矩阵嵌入酉算子。
  2. 设计量子电路,使得测量某个辅助量子比特得到“0”的概率,正比于待测数据投影到主成分子空间后的范数(用于异常检测)。
  3. 设计另一个量子电路,使得测量概率正比于待测数据与训练集数据权重向量的内积(用于距离计算)。
  4. 使用振幅估计来高效估计这些概率。
  5. 最后,使用格罗弗搜索的变体,在训练集中快速找到距离最小的样本。

通过这一系列操作,量子算法可以在某些参数上(如训练集大小P)获得多项式甚至二次加速。然而,实际加速效果需要仔细分析误差容忍度、条件数等参数,并通过数值实验来验证。

变分量子算法

变分量子算法是为当前有噪声的中尺度量子设备设计的。其核心是参数化量子电路,也称为拟设

工作流程是混合式的:

  1. 经典优化器选择一组参数。
  2. 将这些参数发送到量子计算机,运行对应的参数化电路。
  3. 量子计算机通过多次测量,计算出某个期望值(如能量、分类得分)。
  4. 该期望值作为损失函数值返回给经典优化器。
  5. 经典优化器更新参数,重复过程直至收敛。

这种方法类似于训练神经网络,但面临着独特挑战:

  • 贫瘠高原:随着系统规模增大,损失函数的梯度可能指数级减小,使得训练变得困难。
  • 局部极小值:优化景观复杂,容易陷入局部最优。
  • 噪声影响:硬件噪声会扭曲损失函数景观。

研究前沿包括设计具有几何先验(如平移对称性、置换等变性)的拟设,这被称为几何量子机器学习。这类结构可以抑制贫瘠高原,更快地达到过参数化状态,并可能带来更好的归纳偏置。

总结

本节课我们一起学习了量子机器学习的基础。我们从量子比特、叠加、纠缠和测量等核心概念出发,理解了量子计算的基本原理和能力边界。接着,我们探讨了量子机器学习的两大路径:一是基于量子线性代数、具有可证明加速的算法,二是适用于近期设备的变分量子算法。我们还了解了数据编码、量子存储等关键模块,并通过一个案例浅析了量子算法的构造逻辑。最后,我们指出了变分算法面临的挑战和当前的研究方向。量子机器学习是一个充满潜力但尚在发展的领域,其实际优势的实现依赖于量子硬件的进步和算法的持续创新。

007:无仿真生成模型入门

在本节课中,我们将学习现代生成模型的基础知识,特别是被称为“无仿真生成模型”的类别,这包括了扩散模型、流匹配模型等。这些模型是当前许多先进生成式AI的核心框架。我们将从离散扩散模型开始,逐步深入到连续时间模型,并探讨它们在科学领域的应用。

生成模型概览 🧠

当我们谈论生成模型时,可以将其分解为三个核心组成部分:模型本身学习方法以及结合两者的算法。这就像一个“生成模型汤”,不同的配料会带来不同的风味。

例如,模型类别包括你可能听说过的GANVAE扩散模型。过去,这些模型常常面临“生成模型三难困境”,需要在不同特性间权衡。但近年来,扩散模型因其在大规模应用中的出色表现,使得这种困境变得不那么突出。

我们讨论的通用设置是:生成模型处理数据,有时数据带有噪声。我们希望在欧几里得空间 R^N 中表示这些数据,并建模其经验数据分布。这个数据分布就是我们的训练集。

模型类别与学习原则 📚

有多种模型类别可供选择。最明显的一类是完全观测模型,例如自回归模型。它们直接对数据的联合分布进行建模,其优势在于允许因式分解,从而可以计算显式的对数似然。然而,其缺点是生成时需要顺序采样,速度可能较慢。

另一类是隐变量模型,它假设存在未知的隐变量 Z 来生成观测数据 X。这类模型的优势在于可以快速采样,且没有顺序依赖性。但学习过程通常更困难,并且不总是能获得显式的似然。

在学习原则方面,过去人们探索了许多方法,如最大似然估计、期望最大化、变分方法等。如今,最大似然估计 因其良好的可扩展性而成为主流驱动方法。

最大似然估计的目标是最大化观测到数据集的概率,并拟合模型参数 θ。对于自回归模型,这对应着像LLM这样的模型。当我们将模型与学习原则结合时,就得到了具体的算法。例如,隐式生成模型加上双样本检验就得到了GAN;自回归模型加上最大似然就得到了语言模型。

去噪扩散概率模型 🌀

上一节我们介绍了生成模型的基本框架,本节中我们来看看去噪扩散概率模型。你可能已经使用过这类模型,但可能不了解其数学细节。

扩散模型概览包含两个过程:前向过程将数据通过逐步添加噪声进行破坏,直到最终只剩下无结构的噪声;以及与之严格对应的反向过程,它将噪声带回到原始数据分布。

生成建模或学习扩散模型的目标,就是在给定一个预设的前向过程下,学习这个反向过程。扩散模型与其他模型的一个关键区别在于,前向过程是不可学习的,这没有问题,因为我们只需要学习反向过程。

前向过程

R^N 或图像的情况下,前向过程是逐步添加高斯噪声。我们添加足够多的噪声,经过有限的时间步 T 后,最终得到一个标准正态分布 N(0, I)

这个过程由转移核驱动,它将时间步 t-1 的噪声图像带到时间步 t 的更嘈杂图像。我们通过分布 q(x_t | x_{t-1}) 来形式化这个转移核,它由以下正态分布给出:

q(x_t | x_{t-1}) = N(x_t; √(1-β_t) * x_{t-1}, β_t * I)

其中,均值以先前时间步 x_{t-1} 为中心,β_t 是一个随时间递增的方差或噪声调度表。为简化起见,我们假设 β_t 是用户指定的,目前不是一个有趣的参数。

关键约束是:在时间 t=0,我们必须从试图建模的原始数据分布开始;在时间 t=1,我们从标准正态分布采样。对于离散时间版本的扩散模型,我们假设时间被离散化为有限步数 T

无仿真采样

问题是,如果给我一个干净的数据点 x_0,如何生成噪声样本 x_t?朴素的方法是顺序添加一点高斯噪声,但这很浪费。本课程的核心“无仿真生成建模”探讨的是如何在不实际模拟扩散过程的情况下得到噪声样本。

由于我们使用高斯转移核,可以反向展开这个过程。定义 α_t = 1 - β_t,以及 \bar{α}t = ∏^{t} α_s。通过展开,可以将 x_t 重新表达为仅关于原始干净样本 x_0 的函数:

x_t = √(\bar{α}_t) * x_0 + √(1 - \bar{α}_t) * ε, 其中 ε ~ N(0, I)

这意味着我们可以直接从分布 q(x_t | x_0) 中采样,而无需采样中间步骤。这非常重要,因为它避免了昂贵的前向过程仿真。

学习反向过程

扩散模型的主要目标是学习与前向过程相关的反向过程。为了反向进行,我们需要定义反向转移核 q(x_{t-1} | x_t)。根据贝叶斯规则,可以将其表达为后验分布。但问题在于分母中的边际项 q(x_t) 难以计算,因为我们的数据是经验采样,而非真实的密度分布。

然而,如果我们能访问这个转移核,就可以反向模拟随机微分方程来生成图像。视觉上,这就是从一些噪声开始,逐步反向模拟时间,最终得到一个干净样本 x_0

由于边际项难以处理,我们采用另一种方法:假设前向过程中的方差 β_t 足够小,那么反向转移 q(x_{t-1} | x_t) 可以很好地用另一个高斯分布来近似。这样,我们就可以用另一个参数化模型 p_θ 来拟合这个高斯分布。

我们将其参数化为一个高斯分布,并简单地对均值进行参数化。因此,扩散模型试图学习的就是这个反向转移的均值。反向过程也定义为一个过程,它从标准正态分布 p(x_T) = N(0, I) 开始,在每个中间步骤,我们尝试学习一系列高斯分布,其均值由一个参数网络给出,该网络接收噪声样本 x_t 和时间索引 t 作为输入。时间索引很重要,因为它允许我们在所有时间上“分摊”这个过程。

训练目标

如果我们检查反向过程,并假设从一个特定样本 x_0 开始,我们可以写出以 x_0 为条件的反向后验 q(x_{t-1} | x_t, x_0)。这个条件至关重要,因为它告诉我们是从这个样本开始的。

可以证明,这个后验也是一个高斯分布。扩散模型的目标是匹配这个后验分布的均值 \tilde{μ}_t。由于 qp_θ 都是高斯分布,最小化它们之间的KL散度简化为均值之间的回归目标:

L = E[|| \tilde{μ}_t(x_t, x_0) - μ_θ(x_t, t) ||^2 ]

这是一个每时间步的损失,意味着我们可以独立采样一个特定的时间步,计算噪声样本 x_t,然后尝试学习这个高斯分布的均值。忽略与梯度计算无关的常数,这就是扩散模型所做的一切:一个回归目标,在每个时间步独立回归,并按方差平方或其他加权常数进行缩放。

因此,这提供了一种非常自然的扩展方式,因为在学习的任何时候都不需要实际模拟前向或反向过程。我们可以独立地采样时间。

噪声预测参数化

因为我们试图匹配高斯分布的均值,也可以考虑其他有趣的参数化方式。例如,我们可以参数化神经网络为噪声预测网络。可以将高斯均值重新表达为 α_t 的函数,并引入一个新的网络 ε_θ,它试图预测添加了多少噪声。损失函数可以重写为去噪的形式:

L_simple = E[|| ε - ε_θ(√(\bar{α}_t) * x_0 + √(1-\bar{α}_t) * ε, t) ||^2 ]

现在你预测添加了多少噪声。所有改变的是损失函数前出现了一个新的缩放常数。这个缩放常数控制着你优化的是哪些特征。对于图像,恰好可以去掉缩放常数,设为1,结果在生成高质量图像方面表现很好。这在文献中被称为简化扩散目标

训练与采样算法

以下是训练算法的步骤:

  1. 从数据分布 q(x_0) 中采样一个数据点 x_0
  2. 0T 之间均匀采样时间 t
  3. 采样噪声 ε ~ N(0, I),用于构建噪声样本 x_t = √(\bar{α}_t) * x_0 + √(1-\bar{α}_t) * ε
  4. x_tt 输入网络 ε_θ,计算损失 L = || ε - ε_θ(x_t, t) ||^2,并计算梯度。

这极其简单。如果你长时间足够多次地执行此操作,就会学会一个扩散模型。

生成样本时,不幸的是,必须通过模拟进行。这是因为你需要从标准正态分布 x_T 开始,然后逐步去噪。这是扩散模型的一个缺点:在推理时,你需要模拟这个反向轨迹。不过,最近的研究已经致力于通过学习更快的求解器来加速推理。

网络参数化

在实践中,最基本的参数化是使用一个U-Net,你输入噪声样本 x_t,同时也输入时间概念,时间通常通过正弦嵌入进行编码,并馈送到U-Net的每一层。本质上,U-Net预测 ε 或充当去噪器。这是一个非常简单的参数化。

基于分数的生成模型 🎯

上一节我们深入探讨了扩散模型,现在让我们完全切换视角,讨论一种非常相关的生成模型——基于分数的生成模型,稍后我们会将两者统一起来。

基于分数的生成模型与扩散模型有相同的设置:试图在欧几里得空间 R^N 中对一些数据进行建模,并拥有N个样本的数据集。

基于分数模型的一个关键区别在于,你可以访问一个能量函数,或者选择将模型参数化为这个能量函数。这个能量函数接收数据点并输出一个标量值。关键点在于,这个能量函数诱导出一个玻尔兹曼分布

p_θ(x) = exp(-E_θ(x)) / Z

其中 Z 是归一化常数。这是一个有效的概率分布。如果你查看其对数似然或所谓的斯坦分数,分数就是关于数据的梯度:

∇_x log p_θ(x) = -∇_x E_θ(x)

这非常有趣,因为它减少了对模型参数化的约束。例如,模型在这里不必是可逆的。

几何动机与学习

问题是,如何从这个分数构建生成模型?想法是:如果你能在空间的每一点匹配分数,那么从几何上看,分数告诉你在概率空间中上升的方向,以便达到分布的一个模式。因此,如果你能在空间的每一点掌握数据分数,就可以沿着这个向量场前进,直到得到一个样本。

然而,这个形式化存在一个问题:在实践中,我们无法访问这个数据分数。原因与扩散模型相同:我们无法计算 p(x),因为 x 来自训练集的经验分布,没有确切的密度。

因此,我们将从扩散模型中汲取灵感,学习一个去噪分数。在介绍如何学习之前,我们先快速了解一下数据分数的几何动机:空间中的每个点都可以附加一个小向量,它告诉你想要遵循的方向,直到达到分布的一个模式。

去噪分数匹配

一个关键的创新是去噪分数匹配的想法。我们将用有限噪声尺度的高斯核来扰动干净数据,并为每个噪声尺度学习这个去噪分数。为此,我们必须构建数据的边际分布。这个边际分布就是你从数据分布中采样,然后向其添加高斯噪声。

如果你这样做,结果证明你能在空间中获得最优覆盖。因为由于高斯扰动,密度会扩散开来。如果你估计这些点中每一点的分数,就总有一个向量场可以遵循。

在实践中,我们取这个去噪核的分数,并尝试匹配它。之所以可能这样做,是因为当你添加高斯噪声时,对这个分数有一个非常好的闭式表达式。梯度 ∇_x log q_σ(x̃ | x) 简单地是 -(x̃ - x) / σ^2

与传统的分数匹配情况不同,去噪分数恒等式在这里给了你明确的分数访问权限,并为你提供了一个可行的回归目标。

因此,这里的步骤是:采样一个数据点 x,向其添加一些噪声,然后尝试在每个噪声级别拟合分数估计或分数网络。如果你这样做并拥有一个完全训练的分数网络,实际上就可以沿着向量场反向进行。

采样过程

采样过程是:遵循这种随机微分方程,我们迭代地遵循对数数据密度的梯度,并添加一点噪声以保证一定的随机性。如果你运行这个过程足够长时间,保证会从数据分布中命中一个样本。当然,我们无法访问这个实际的分数函数,所以我们将用我们学习的分数函数(神经网络)来替换它。

在实践中,采样看起来就是:取你的分数网络,遵循它一小段时间,添加一点噪声。这就是基于分数的生成模型进行采样的全部。视觉上,粒子慢慢收敛到实际的模式。

统一视角:连续时间扩散模型 ⚙️

现在,我想再次切换视角,向你展示离散时间扩散模型和基于分数的生成模型实际上是同一回事。我们可以通过将扩散模型视为随机微分方程来理解这一点。

之前我们说,在扩散模型中,我们会取离散数量的时间步。如果你让时间趋于无穷,时间离散化变得无限精细,那么扩散模型实际上就是随机微分方程。

具体来说,一个SDE有两项:漂移系数 μ 和扩散系数 σ。漂移系数对应于轨迹的均值,扩散系数告诉你围绕该均值添加了多少噪声。B_t 是布朗运动,可以简单地认为是该点的时间相关高斯噪声。

这个SDE视角为我们提供了什么?它为什么与常微分方程不同?区别在于,ODE的求解方式是通过迭代解进行,而SDE也有一个迭代解,但你还必须添加噪声项。

如果我们将扩散模型带到这个噪声时间极限,它们看起来像什么?这里显示的随机过程正在破坏图像,这是前向过程。我们试图在结束时再次达到一个模式,即标准正态分布 N(0, I)。在结束时,我们注意到它几乎没有结构。

就像在离散时间DDPM中一样,我们想要学习对应于这个随机过程的反向扩散过程。幸运的是,因为我们有一个非常好的SDE形式,早在1982年Anderson就证明,你只需要分数函数。这正是我早先展示的基于分数的生成模型中的相同分数函数。

这两个SDE是联系在一起的。对于上述形式的每个前向SDE,都有一个对应的、唯一的反向扩散SDE,它具体取决于这个数据分数和另一个时间反转的布朗运动项。这意味着,如果你学习了连续时间的分数函数,实际上就拥有了一个连续时间扩散模型。

那么如何学习分数函数呢?正如提到的,可以通过去噪分数匹配或其他方式。但与之前的关键区别在于,我们首先均匀采样时间,然后添加转移,然后在每个时间尺度匹配它。还有一个加权项 λ,但就像扩散模型一样,λ 控制你想赋予各种特征的权重。

从某种意义上说,大多数连续扩散模型只是在做这件事。如果你学习了分数,反向扩散过程与前向过程有非常相似的特点:我们从无结构噪声开始,最终得到不同的干净图像。你可以看到轨迹的均值慢慢收敛到分布的模式,但布朗运动增加了许多随机性,这允许你采样许多不同的多样化事物。

流匹配模型 🌊

我想再次切换视角,介绍或许是我最近最喜欢的生成模型类别——流匹配基于流的生成模型。它们与扩散模型非常相似,因为它们也是无仿真的,但要让它们无仿真,我们需要做一点工作。

像之前的所有幻灯片一样,我想做一点设置。但这次设置略有不同:我们拥有通常的数据 x ∈ R^N,但也有一些隐变量 Z。我们想学习这个双射函数,即从 R^NR^N 的可逆函数。

如果你学习这个双射函数,可以问它如何对分布进行操作。如果你取一个随机变量 Z 并对其样本应用函数 f,输出分布或相应的分布是什么?事实证明,因为函数是可逆的,输出分布通过概率密度的变量变换公式Z 上的初始分布相关联。

这个变量变换公式表达非常简单。我可以在代数上解释它在做什么,但我更愿意在几何上解释。假设你的初始数据分布是这个正方形,你的函数 f 将其映射到这里的矩形。因为我说这些小的矩形或正方形是概率,它们必须加起来为一。如果你选择一个特定的 f,矩形的形状就会受到限制。

在这个例子中,你的 x 是均匀分布 U(0,1),将你映射到 y 的函数是一个简单的函数 2x + 1。如果你采用这个构造,最终会得到这种平行四边形。这个平行四边形本质上将你从一个空间带到另一个空间,这是由于你的函数 f 造成的扭曲空间。行列式项只是告诉你空间扭曲了多少,以便你可以通过拥有有效的概率密度来纠正它。

标准化流与连续标准化流

这就是标准化流。之所以称为标准化流,是因为它标准化为一个有效的概率,而“流”是因为你从一个概率分布“流”到另一个。如果你有一系列这些函数 f,你可以从一个易于采样的 z_0 开始,通过一堆 f 传递它,得到一个更复杂的样本 z_k。它们都通过变量变换公式相互关联,但每个函数应用一次。

在流匹配之前,构建这种标准化流的关键设计考虑是如何明智地选择你的函数 f,以便最终的对数行列式项易于计算。

就像在扩散模型中一样,如果你将离散时间或离散函数数量带到连续函数数量,会发生什么?事实证明,你会得到一个所谓的连续标准化流。连续标准化流本质上是一个常微分方程。你的时间步 z_t 的样本是你的 z_0,但现在你通过遵循这个函数 h 从时间 t_0 积分到 t_1

因为它是常微分方程,根据构造它是可逆的。有一个对应的时间反向动力学,只是你之前积分的负值。它们都相互关联,因为你可以将其表述为这个ODE:dz_t / dt 就是一个ODE。因此,如果你有一个学习的函数 h,生成样本的方式就是沿着这个ODE正向(或更确切地说,反向)积分时间。

作为一个考虑,记住我们有作为SDE的扩散模型。连续标准化流只是一个ODE。它完全一样,你只需去掉 G 项并按不同的常数归一化,它本质上在做同样的事情。但流提供的一个关键好处是扩散模型无法提供的:你可以显式计算对数似然。扩散模型本身不能给你对数似然。

那么为什么扩散模型可能比连续标准化流更受欢迎呢?原因是它们训练时是无仿真的。你不需要模拟整个过程来训练扩散模型。而我展示的流的当前公式,实际的对数似然项是必须发生的变量变换,而这个变量变换必须有这个积分。如果你必须在每个损失时间步都做这个积分,那将非常昂贵。这就是为什么CNF很难扩展的原因。然而,ODE通常有更容易的推理,因为你不必担心噪声,所以在数值上模拟ODE比SDE更容易。

流匹配:实现无仿真训练

一个自然的问题是:我们如何让CF变得无仿真?这就是整个流匹配思想的用武之地。

我们如何以无仿真的方式训练CNF?我们采用与扩散模型相同的原则。我们说,我们将前向ODE定义为一个概率路径,它从先验开始,将遵循一个ODE,该ODE也慢慢向数据添加噪声,直到你达到非结构化先验或完全噪声。学习的目标将是逆转这个过程。

就像扩散模型一样,这个前向过程或前向ODE将是不可学习的。它将被规定、固定。我们将只寻求学习反向生成过程。

为了给这个加上符号和术语,概率路径是时间相关的概率测度。是一个函数 φ_t,它将你从数据空间带到另一个数据空间,并且是这个常微分方程的解。我希望你关注这个常微分方程中的关键思想或对象是右边的项,即这个时间相关的向量场 u_t。另一个方面是你还必须满足初始条件。

关键问题是,我们如何学习定义这个ODE的向量场?人们可能会天真地认为,我们可以用一个学习到的模型直接回归到这个向量场。我们参数化一个模型 v_θ,称之为向量场网络,并希望直接回归到 u_t。不幸的是,就像扩散模型一样,我们不能天真地计算这个。原因完全相同:首先,可能不止一个边际向量场 u_t;其次,因为我们只有数据样本,我们实际上无法为此构建显式形式。

因此,我们将做的是构建条件流匹配目标。这个想法非常直观。我们选择一个点 x_1,选择一个点 x_0,然后说我们将定义 x_0x_1 之间的最短路径。然后我们问,当我们以线性速度沿着这条路径行进时,向量场是什么?如果我在这条路径上选择一个随机点 x_t,并且说,如果 x_t 是一个以线性速度从 x_1 行进到 x_0 的粒子,对应的向量场是什么?

从几何上讲,这意味着 x_t 在时间 t 必须行进更多的时间,它必须行进的方向是 x_t - x_0。如果你将这个量除以时间,它对应于一个有效的向量场。如果你知道物理,位置的时间导数将给你速度。所以这里是一样的想法。你选择两个点 x_1x_0,定义一条最短路径。因为你先验地选择了这些点,你知道如何构建最短路径。然后你对你的向量场有一个闭式解。这将是一个回归目标。

因此,之前的不可处理目标,如果你做条件流匹配,现在可以变得可处理,其中 z 本质上是条件于你路径的两个端点。奇迹般地,事实证明这两个目标具有相同的梯度。所以当你做这个条件流匹配的想法时,你一点也不会失去一般性。但很酷的是,第一个目标不可计算,第二个可以,并且它给你一个有效的学习目标。而且,如果你愿意,你总是可以通过边际化恢复目标无条件路径或目标无条件向量场。

流匹配的变体

在图片中,流匹配在做什么?流匹配有多种变体。第一个最明显的是由Aaron Lipman等人提出的。想法是,我们只条件于一个端点,然后希望有足够的时间能到达另一个端点。条件于两个端点的条件流匹配想法显示在中间图中。正如你所见,路径在性质上略有不同。因为你正在构建路径,它们保证能到达每个端点。

最后,因为你选择了路径并构建了路径,你可以控制路径是如何形成的。从某种意义上说,你可以做的是,你可以将路径优化为预处理步骤,以便它们在全局上是最短的。这意味着,与其选择两点之间的最短路径,你可以优化这个目标,使得在整个数据集上,你学习的所有路径在全局上是最短的。这恰好是流匹配的最优传输版本。

你可能想这样做的原因是,因为如果你有全局更短的路径,你的损失可能会有更低的方差。这就是流匹配的思想。它和扩散模型完全一样。一旦你学会了这个向量场,生成样本的方式就是反向模拟这个ODE。与扩散模型不同,你不必担心添加布朗运动或布朗噪声。

实际应用案例 🚀

以上是讲座第一部分的内容。现在我想谈谈,人们在实践中实际上用这些模型做什么?我们已经看到了图像、疯狂的视频生成、文本条件模型。但这个框架的通用性远不止于此,其美妙之处在于它适用于你可能想到的更多数据类型。

例如,我个人感兴趣的问题之一是生成蛋白质的想法。这里有一个学习蛋白质扩散模型的例子。右边是一个学习蛋白质流匹配模型的性质不同的例子。你会注意到你学习的轨迹类型在性质上是不同的,但模型本质上是相同的。在某种意义上,两者都具有同等的可扩展性。

类似地,流匹配的一个优点是,因为你选择了端点,你对起始分布有更多的灵活性。它不一定总是正态分布 N(0, I),也可以是一个经验数据集。所以你可以直接进行条件图像插值。在这个例子中,我们模拟从狗到猫的转换,这是在某个稳定扩散模型的潜在空间中进行的。想法是,你想选择那些紧密反映真实情况的奇特轨迹。两列是在各种约束下的流匹配模型的两个不同实例。

你也可以处理点云或更几何的数据。你可以有直接穿过数据流形的路径,或者围绕数据流形的路径。流匹配框架的美妙之处在于,因为你控制路径,你可以告诉路径你的数据中的约束是什么,这样你就可以向数据添加约束,以便围绕已知结构进行变形。

类似地,这里有一个例子,说明如何更好地尊重数据流形。这是一个单细胞RNA的例子,本质上是一个时间演化过程,一个生物过程,是破坏性的。目标是,当你在时间上缺少快照时,学习这些轨迹。每种颜色代表不同天的观察结果。在实践中,你只有部分观察结果,缺少一些天数。生成建模的目标是重建这个缺失的轨迹。这确实是路径构造如何真正有用的一个例子,因为你可以选择更好地拥抱数据流形的路径。

最后,我想以我个人最喜欢的、即将到来的应用结束:扩散模型和流匹配模型也可以推广到离散数据。我不会给出如何做的例子,但我会留给你一个笑脸。本质上,它是在概率单纯形中生成这个笑脸,概率单纯形的每个角对应一个独热标记或某个词汇表,比如语言模型或某些离散值数据空间或分类分布。

总结 📝

在本节课中,我们一起学习了现代无仿真生成模型的基础知识。我们从离散扩散模型及其训练、采样算法开始,理解了其核心是通过回归目标学习反向去噪过程。接着,我们探讨了基于分数的生成模型,看到了如何通过去噪分数匹配来学习数据分布的梯度场,并用于采样。

然后,我们通过连续时间随机微分方程的视角,统一了扩散模型和分数模型,认识到它们本质上是相通的,都依赖于对数据分数(梯度场)的学习。最后,我们介绍了流匹配模型,它通过构建和匹配条件向量场,实现了在标准化流框架下的无仿真训练,并提供了更灵活的路径控制和可能性。

我们还看到了这些强大框架在蛋白质设计、图像插值、点云生成和生物时间序列建模等科学领域的激动人心的应用。这些模型的核心思想——学习一个将噪声转化为数据,或将数据从一个分布转化为另一个分布的(随机)动力学过程——为处理各种类型的数据提供了统一而强大的范式。

011:定理证明的机器学习

在本节课中,我们将要学习机器学习如何应用于定理证明。我们将从定理证明的基础概念开始,探讨其重要性,然后深入讲解如何利用机器学习技术来自动化证明过程,并分析该领域的最新进展与未来方向。

定理证明基础 🧮

定理证明,顾名思义,就是陈述一个定理并尝试证明它。我们在过去的数学课程中经常这样做。例如,一个定理可以是“加法满足结合律”。

我们可以更明确地写出结合律的含义:∀ a b c, a + (b + c) = (a + b) + c

看到这个,我们可能立刻会想,根据基本代数,这显然是成立的,无需写出证明。但有时,我们可能被要求用归纳法写下证明,这感觉像是为正确性提供了严谨的论证。

然而,这仍然是我们认为的非形式化定理证明。要以形式化的方式进行,我们可以通过编程来实现:归纳地定义自然数和加法,陈述定理,并编写程序化的证明。

你可以在证明助手中完成所有这些,例如 Coq、Isabelle 或 Lean。这些工具允许用户进行交互式定理证明:他们陈述定理、证明它,并且证明会经过机器检查以确保正确性。

那么,这些证明助手是如何工作的呢?它们通常基于某种逻辑或类型论基础。在它们中证明一个定理,相当于应用形式化系统的规则,将目标(初始状态就是定理本身)逐步转化,直到它变得平凡。

虽然可以直接在证明助手的逻辑中证明定理,但这会非常困难且苛刻。因此,这些证明助手提供了高级的策略,如归纳法(induction)和自反性(reflexivity),来帮助引导证明过程。

为何使用证明助手?🔍

证明加法结合律是相当基础的内容,并不特别有趣。那么,为什么有人想使用证明助手呢?两个流行的应用包括形式化数学形式化验证软件

为了用证明助手形式化验证软件,程序员可以编写他们的软件,指定一个关于其软件属性的定理,并尝试证明他们的代码符合该定理。例如,他们可以定义一个列表数据结构,并编写像反转(reverse)这样的列表函数。

然后,他们可以尝试证明关于 reverse 函数行为的属性,例如:反转两个列表的连接等于第二个列表的反转与第一个列表的反转的连接。或者,甚至是一个属性:列表的反转的反转是原列表本身。

接下来,我将通过例子来说明形式化数学和形式化软件验证的力量,以激发你使用它们的动机。

形式化数学与软件验证的力量 💪

近年来,形式化数学在数学家中获得了一些流行,因为它可以帮助数学提供更严格的论证。证明可以被轻松检查,这对人类独自完成来说是极其困难的。有些人甚至设想未来提交给数学期刊的每个证明都附带一个形式化证明。

这个领域最近的一个成功案例是“液态张量实验”,其中 Schultz 在线提出了一个挑战,要求形式化一个关于凝聚实向量空间的定理。Kamein 和他的团队接受了挑战。许多本科数学内容已经在 Lean 证明助手中被形式化,存储在名为 Mathlib 的库中。这个形式化基础使得对这个关于复杂对象的复杂定理的形式化成为可能。

另一方面,使用证明助手进行形式化验证已被证明在实际生产更高质量软件方面是有效的。我们都认同软件中的错误是糟糕的。

例如,在 2011 年,一个名为 C Smith 的项目测试了三个 C 编译器:经过 Coq 验证的 C 编译器 CompCert 和两个行业标准编译器 GCC 和 LLVM。该项目发现,CompCert 是唯一一个 C Smith 无法找到任何错误代码错误的编译器。这是一个相当酷的结果。

虽然形式化验证不能保证完全没有错误,但深入思考代码、规范和证明的过程,相比于仅仅编写一些测试用例,能够产生更少错误的代码。

自动化证明的必要性 🤖

然而,可以预见的是,即使有交互式定理证明器的帮助,编写证明所需的努力对于软件验证来说往往是令人望而却步的。验证代码需要大量时间,并且证明的规模通常远大于代码本身。

例如,对于经过验证的 C 编译器 CompCert,其证明规模是编译器代码本身的八倍多,并且花费了三人年的工作量。同样,为了在 Isabelle 中验证一个 OS 微内核,编写证明脚本花费了 11 人年。

因此,由于验证的巨大开销,如今公司发布的所有软件几乎都是未经验证的。液态张量实验的成功也花费了两年时间,并且因为直接证明定理太困难,其工作被分成了两部分。

那么,作为程序员,我们通常如何处理困难的任务呢?我们会尝试自动化它们。在软件开发流程中,许多方面都可以通过自动化得到帮助,形式化数学也是如此。

但就本次讲座而言,我们将主要关注如何自动化证明的编写。目前在大规模基准测试上的评估,通常假设你已经完美地完成了大部分(如果不是全部)前期过程。这并不总是一个好的假设。

因此,我将首先介绍在没有自动化技术的情况下,在定理证明环境中编写证明是怎样的体验。然后,我将讨论已集成到证明环境中的自动化技术。最后,我们将探讨如何使用机器学习来模拟人类与证明系统的交互。

交互式证明环境体验 👨💻

想象一个程序员选择使用 Coq 证明助手,并希望编写一个证明脚本来证明某个定理(这里称为 spec),甚至可能是关于某个软件的属性。使用 Lean 或 Isabelle 证明助手将是类似的体验,只是策略名称可能不同。

在指定定理之后,他们以 Proof. 策略开始。Coq 会提供反馈,即当前的证明状态。这是一个为了示例而简化的视图。在这种情况下,有一个子目标,即定理本身,尚未被证明。

程序员可以检查证明状态,批判性地思考它,然后应用更多的证明策略来引导 Coq 解决目标。例如,我们看到目标中存在 ∀ n。因此,程序员可以假设存在某个任意的 n,并应用 intro 策略来消除全称量词。

现在,我们看到自然数 n 在假设的局部上下文中。假设程序员想使用归纳法(induction)。这将使他们有两个目标需要解决:基础情况和归纳情况。

程序员可以继续应用策略来解决目标,直到最终没有更多目标剩下,然后他们可以应用 Qed,从而完成定理的证明。

这种来回交互有助于引导程序员找到证明。但你可以想象,对于非常复杂的证明,这可能相当耗费精力。

现有的自动化工具:Hammers 🔨

因此,有一些工具可以集成到这些证明助手中,也有完全使用基于约束求解的证明自动化的其他语言。事实上,我们稍后会看到,被称为 Hammers 的工具可以与机器学习方法互补,甚至成为其组成部分。

Hammers 在尝试“敲定”证明时,会调用 SMT 求解器。它们尝试在自己的逻辑中构建证明,如果成功,则将其翻译回证明助手的逻辑中。然而,它们无法推理诸如归纳法这样的证明方法,并且难以处理高阶逻辑。

因此,虽然它们有时很强大(例如,大约四分之一的 Coq 基准定理仅通过调用 Hammer 就能解决),但它们的能力受到很大限制。所以,基于机器学习的工具对于定理证明很重要,因为它们让我们能够超越这些一阶求解器,继续推动证明书写自动化方面的可能性。

基于机器学习的工具可以证明超过三分之一的 Coq 基准定理。因此,虽然改进有时看起来是渐进的,但它可能节省大量的努力。

定理证明对 AI 发展的重要性 🧠

如果所有这些还不够有说服力,那么还有一点需要注意:定理证明对于推进人工智能也很重要。

正如你之前听到的,我们正处于大语言模型的热潮中,它们已经在广泛的任务上展示了相当惊人的能力。具体来说,LLMs 已经证明它们能够处理非常困难的问题,例如生成代码,并展示了数学推理能力,特别是那些经过训练以解决定量推理问题的专业数学 LLM。

但是,虽然 LLMs 能产生好的答案,但挑战在于它们也会产生格式良好、令人信服的错误答案。例如,你可以尝试测试 LLM 生成的代码,但通过测试仍然可能不代表正确性。这使得在大多数领域难以可靠地使用 LLMs,也难以信任它们的输出。

然而,证明助手可能为这一挑战提供解决方案。记住,它们充当机器检查证明的“预言机”。如果 LLMs 生成了看起来令人信服但错误的答案,而你有一种方法可以形式化该输出并编写证明,那么定理证明器可以识别并标记那些错误的答案。同样,当生成许多候选证明时,它只会接受有效的证明。

这使得定理证明成为 LLM 使用的一个非常强大的领域,并且也许可以作为我们整体评估 LLM 推理能力的一个领域。

机器学习自动化定理证明概述 📋

本次讲座的其余部分将致力于讲解如何使用机器学习来自动化定理证明。

以下是接下来内容的概要。首先,我将介绍机器学习用于定理证明的基础知识,并展示一个将这些基础知识结合起来的例子。然后,我将探讨人们思考如何超越这些基础的一些方法,思考是否存在最佳方式,以及我之前展示的流程中的其他部分如何融入,特别是人类在其中扮演什么角色。毕竟,他们才是需要证明定理的人。

那么,让我们开始吧。

机器学习定理证明基础 🏗️

现有的工作已经构建了执行下一策略预测甚至完整证明预测以合成证明的模型。

通常,这些模型将定理当前证明状态和一些上下文信息作为输入,并预测证明的下一步可能步骤。通常,你可以对前 K 个(例如三个)策略进行采样,然后使用证明助手来检查应用这些策略后产生的候选证明。

证明系统以更新后的证明状态形式提供反馈,并显示是否存在任何错误。因此,这再次体现了完整的交互性质。对于那些没有导致错误或重复证明状态的候选,如果状态显示没有更多目标需要证明,那么我们就成功地证明了定理;否则,我们继续将它们作为输入提供给模型以生成后续步骤。

为了提供一个不同的视角,这里是我们搜索树的起点,其中节点是应用策略后产生的状态,箭头是策略。在所谓的元启发式搜索中,模型使搜索偏向于可能的下一步。它可以是深度优先搜索、广度优先搜索等。我们继续搜索,直到我们的某个候选证明脚本没有更多目标需要证明,并且我们可以预测 Qed

因此,这种方法在很大程度上取决于模型本身的好坏,因为是它在引导搜索。

获取高质量训练数据 📊

因此,尝试获得一个好模型的一种方法是获取高质量的训练数据。这些训练数据可以来自其他人证明定理的经验。对于我之前提到的三个证明助手 Isabelle、Coq 和 Lean,都有公开可用的数据,范围在 10 万到 30 万条定理之间,并且每个证明助手都有相关的人工编写的证明。

然而,不同的定理证明器有不同的社区支持,这导致了每个证明器可用的数据类型不同。例如,Lean 在数学社区中备受关注,并且有著名数学家的积极参与,因此那里有很多形式化的数学。而 Coq 则是软件验证的热门选择。

因此,研究人员决定为哪个证明器训练模型,有时会限制与先前工作的比较,因为他们基本上只能使用该证明环境的数据。不过,最近有一些努力旨在创建跨不同语言的基准测试,以允许进行更多此类比较。

一旦你选择了一个证明助手,你通常希望生成示例,以便以监督学习的方式训练你的机器学习模型。例如,查看 Lean 的 Mathlib4 库,假设这是一个定理及其人工编写的证明。那么对应的训练示例可以只是将定理作为输入,整个证明作为目标。

然而,这样做可能会错过可以从中学习的中间状态。因此,研究人员通常通过逐步运行人工编写的证明,并对齐当前状态和人类应用的下一个策略来提取数据。因此,由于大多数证明通常不止一步,如果你对 Mathlib 库进行数据提取,训练样本的数量会超过定理数量的两倍。

前提检索与模型输入 🔍

回到示例创建,我们知道证明环境中还有其他可能有用的信息可用于建模。例如,在目标序列中,你可以看到 add_assocadd_comm 这样的引理。如果不将它们作为输入提供,模型将不得不“幻觉”出它们的存在。

因此,已经证明的引理和逻辑事实(有时称为前提)很有用,应该包含在输入中。我们可以在训练时使用真实的正例前提,但在测试时,我们需要一种方法来检索有用的前提。

前提检索可以使用稀疏或密集段落检索技术来完成。由于检索将在今天稍后的讲座中介绍,我不会花太多时间在这上面,只提供一些直觉。在证明环境中,仅仅通过导入不同的库,就可能存在成千上万的前提可用。因此,对于给定的证明状态,你希望检索对该证明状态最有用的前提。你可以训练一个编码器来处理正例和负例前提,编码所有前提,并编码证明状态,然后选择那些与证明状态具有最大余弦相似度的前提作为模型的输入。

其他工作还探索了训练所谓的重排序模型,因为优先考虑哪些前提能进入输入也很重要,因为有时输入空间有限。

模型架构的选择 🏛️

另一个有助于构建好模型的因素是合适的架构,以表示证明环境的不同方面,主要是证明状态、前提,甚至可能包括你当前正在处理的证明过程。

早期的工作始于 RNNs。其他工作使用了证明状态的底层抽象语法树表示,因此使用树状 LSTM 来表示它。最近的工作甚至使用了 GNNs,因为不同定义和引理之间的关系可以用图来表示(还记得之前那个大的依赖图吗?)。但最明显且最近被探索的是Transformer,特别是基于 Transformer 的预训练大语言模型。这再次证明了定理证明是 LLM 应用的一个好领域,并且正变得越来越流行。

搜索策略:最佳优先搜索 🔎

一旦我们有了一个模型,我们如何使用它在可能的证明空间中搜索呢?回想一下我们的搜索树,它可能很快变得难以管理。因此,朴素的广度优先搜索难以找到深度证明,需要一些优先级标准来平衡树的广度和深度。

因此,最佳优先搜索方法对于优先扩展树的哪些节点很有用。我们希望选择最有希望的未探索状态进行扩展。例如,这里是未探索的节点。在一个简单的最佳优先搜索实现中,我们可以使用从策略生成器沿路径累积的分数。对于每个预测,我们可以从模型中获得一个对数似然分数,然后使用沿路径的总和作为该特定节点的分数。

这些是一些基础知识。现在我将展示一个以这种方式实现了这些基础知识的工具。

工具演示:Lean Copilot 🛠️

这里是一个编辑器,它导入了 Lean Copilot 工具。右边显示的是证明状态。如果你从这里的一个定理中删除一个策略,并要求 Lean Copilot 建议一些策略,它会在右边给出一些建议,比如“试试这个”。我们看到,很好,这个策略确实有效,并且那个证明中没有更多目标了。

或者,你可以删除整个证明,并要求它搜索证明。哦,很好,它找到了一个。不过,有时当你尝试搜索时,它会给出一个错误,无法找到完整的证明。所以,也许你想提供一些你认为好的策略,比如你知道你想提供证明的某个前缀,然后要求它搜索其余部分。哦,它做到了,它能够完成。

为了了解每一步发生了什么,你还可以查找对该证明状态有用的前提。所以,右边是证明状态,底部是一些被模型认为与该证明状态相关的前提。是的,Lean Copilot 可以在线使用,我认为它是一个非常酷的工具。

超越逐步生成:完整证明与修复 🔄

那么,这是唯一的方式吗?我们能否超越这种方式?我们甚至可以问:我们真的需要搜索吗?

最近的工作表明,如果我们微调一个大语言模型来一次性预测整个证明(而不是逐步进行),并用证明助手检查这些证明,我们仍然可以做得很好。如果我们微调一个大语言模型来修复错误的预测,利用证明器输出的错误信息尝试生成新的证明,我们甚至可以做得更好。

这里的修复并不是你通常认为的修复(比如编辑某一行)。相反,它被视为一次完整的重新生成。因此,现在的生成过程以前一次失败的尝试和错误信息为条件。

利用非形式化推理与自动形式化 📝

我们甚至需要微调步骤吗?这些模型在推理方面似乎非常出色,以至于在某些任务中,上下文学习就足够了。此外,通过不同的提示框架,如思维链,我们经常尝试从 LLMs 中引出非形式化推理,以提升它们在许多任务中的性能。非形式化推理数据是它们训练数据的主要部分。它们可能在预训练中看到一些 Coq 和 Lean 证明的例子,但与非形式化推理和自然语言相比,这非常少。

就网上可用的数学数据而言,非形式化数学数据构成了可用数学数据的绝大部分。那么,为什么不利用这一点呢?对于一个非形式化陈述,生成一个非形式化证明对 LLM 来说似乎不是一个巨大的飞跃。而直接从非形式化一步到位生成形式化证明,似乎可能会在翻译过程中丢失很多信息。

通常,这种从非形式化到形式化的翻译被称为自动形式化。因此,最近的工作提出了一个中间步骤,称为形式化证明草图,其中有一些需要填补的“空洞”,这些空洞对应着非形式化推理往往含糊其辞的明确证明步骤。我们可以通过策划少量示例来生成这些证明草图。

让我们看看这个方法的实际应用。这里我们有 Draft, Sketch, Prove 方法,它实现了这种技术。它从一个 LLM 开始,通过少量提示非形式化地证明一个陈述。然后,通过少量提示之后生成形式化证明草图,将证明分解为不同的子目标。值得注意的是,这项工作是在 Isabelle 中完成的,它支持更具声明性的证明风格,因此往往更具人类可读性,无需运行证明并查看证明状态,实际上证明状态在 Isabelle 证明中是明确显示的。

这样,你所使用的证明助手语言实际上可能适合这种自动形式化技术。也许这种技术在不同的证明助手中不会那么有效,因为形式化和非形式化之间的对齐可能没有那么好。

最后,为了填补草图中的空洞,可以使用现成的证明器,比如我之前提到的 Hammer,或者其他基于机器学习的下一步预测工具。这例证了不同技术如何可以结合在一起,发挥各自的优势。

集成学习与在线学习 🤝

因此,这里展示了进行定理证明的多种不同方式,当然并非全部。这是一个快速发展的领域。有时很难判断哪种方式是最好的,特别是如果它们都在不同的基准上进行评估。

那么,我们如何确定哪种是最好的呢?实际上,在某种程度上,是否存在最好的方式甚至重要吗?我在这里要说,也许不重要。因为只要这些方法能证明不同的东西,你实际上可以把它们全部一起使用。

例如,假设你有多个不同的定理证明模型,每个模型都对给定定理进行自己的证明搜索。当你检查输出时,即使只有一个证明是正确的,证明助手每次都会挑出那个证明。所以,你的模型可以以不同的方式、在不同的数据上、使用不同的特征或超参数值、不同的搜索程序等进行训练,你可以从每个模型中获益最多。因此,定理证明也是集成学习的完美应用。

但是,与其广泛撒网并使用从他人证明中学习到的模型,定理证明中的另一种技术是使用在线学习来适应你的特定项目。

一个名为 Tactician 的 Coq 工具研究了不同的流行在线机器学习技术,例如用于在线近似 KNN 的局部敏感哈希森林和在线随机森林。这是为了更好地为特定项目合成证明。它通过选择已在项目中其他证明中使用过的策略来重用,从而实现这一目标。为了使这有效,你实际上已经需要编写一些证明。但也许它可以帮助你填补其余的证明。

自动化流程的其他方面:猜想与自改进循环 🔄

我主要关注的是证明生成这个狭窄的部分。但请记住,在你到达那一步之前,有一整套流程必须发生。关于这个流程,还有其他方面可以使用机器学习进行自动化,这些方面探索较少,但正在开始,这是令人鼓舞的,即猜想

猜想的意思是,从你文件中的定义出发,你可以使用一个模型来猜想可能成立的定理,然后通过尝试证明它们或寻找反例来检查它们。有一些技术,如基于属性的测试,可以让你为给定定理找出反例,这样你就不会在证明上浪费时间。你甚至可以尝试猜想有用的辅助引理,这些引理可能有助于解开当前的证明。

大多数时候,直到你处于证明过程中,你甚至意识不到你需要一个辅助引理。因此,当前仅仅检索辅助引理的工具并没有考虑到在某些情况下这可能有多不现实。你可能直到那一刻才知道你需要一个辅助引理。因此,与其检索,你实际上必须合成。

猜想也可以作为在定理证明中训练更好模型的一种手段,因为你不需要依赖人工编写的训练示例。在一个名为 Minimo 的工具中,他们使用一个随机初始化的 Transformer 同时执行猜想和证明搜索。

对于猜想,他们使用类型导向的合成和约束解码,以确保猜想的定理在构造上是有效的。当找到一个证明时(证明搜索是通过蒙特卡洛树搜索完成的),它会生成数据来改进策略和价值函数(这是一种强化学习方法)。它还提供数据来改进猜想,因为它随后知道问题的难度。他们利用这一点在猜想和证明改进之间交替进行,形成一个自改进的循环。

游戏智能体方法:AlphaProof 🎮

探索游戏智能体方法的另一个例子是 Google DeepMind 的 AlphaProof。它将一个预训练的语言模型与 AlphaZero 强化学习算法相结合,该算法之前曾用于自学掌握国际象棋和围棋等游戏。

在这里我们看到,当他们尝试形式化非形式化数学问题时,一个求解器网络会搜索这些问题的证明甚至反证,并通过 AlphaZero 算法逐步训练自己解决更具挑战性的问题。这与猜想有相似之处,但这里你是在进行自动形式化:你有一些非形式化的东西,也许通过形式化过程,它可能变得不成立,因为你没有正确地形式化它,但你可以多次尝试,也许其中一些会成立。

结合他们的 AlphaGeometry 工具,它实际上在今年国际数学奥林匹克竞赛中获得了银牌级别的成绩,解决了六道题中的四道,这是自动化定理证明的一项相当令人印象深刻的成就。

人类在流程中的角色与工具设计 👥

回想一下,定理也可以类比于软件规范。最终,只有人类才能最终决定程序的期望规范是什么。不过,机器学习工具可能有助于从程序中推断规范,这对人类在检查那是否是期望的行为时可能有用。

说到人类,他们如何融入这一切呢?当你想到这个执行类似软件工程任务的人类证明工程师时,你可能会想,他们真的这么有纪律,按照这个顺序遵循开发步骤吗?因为我肯定不是这样写代码的。知道证明工程师也和我们一样,可能让你感到安慰:他们也不总是这样写代码。

一项案例研究发现,证明工程师在编写代码和证明时,会不断地完善和重构规范,并进行重复的修复。因为一个曾经有效的证明可能会因为规范演变甚至依赖项更新而失效。事实上,他们甚至可能从编写代码开始,然后才考虑生成定理。

因此,由于这种严格的顺序可能并不总是适用,那么开发能够很好地集成到从业者工作流程中的、基于机器学习的工具就至关重要,需要考虑他们如何在各个流程之间移动。这只有通过与证明工程师持续对话才能实现。我们不能孤立地进行研究,我们需要与他们一起进行研究。

因此,未来在这一领域取得有意义的研究进展,可能不仅仅取决于你的工具能证明给定基准中已经证明的定理的多少。而是你的工具如何作为协作者帮助人类形式化数学或验证他们的代码。

改进工具反馈:Proofster 示例 💡

说到协作者,我们提到了 Lean Copilot 工具,以及当它无法自动找到证明时,它并没有提供任何反馈来帮助用户,这有点令人失望。它只是说“做不到,抱歉”。

一个尝试思考其他类型反馈会有用的工具例子叫做 Proofster。它为证明搜索工具提供了一个前端 Web 界面。目前,它为名为 Proverbot 9001 的工具(一个用于 Coq 的基于机器学习的证明搜索工具,使用 RNNs,而不是花哨的 LLMs)提供了前端界面。

我将向你展示它的功能,以及它如何帮助调试这些失败的证明搜索。

这里,有人想证明关于列表的某个属性,但省略了证明本身。然后 Proofster 可以尝试完成它。当我们要求 Proofster 证明这个定理时,它也说“抱歉,我做不到”,但它不会让你空手而归。它提供了一个交互式搜索树。所以你可以看到在它的证明搜索中尝试了哪些策略。

这是树的起点,你可以点击蓝色的小节点来展开它们。查看这棵树,我注意到证明搜索尝试了对不同变量进行归纳,但它没有对正确的东西进行归纳。这是这些机器学习模型非常常见的失败模式。

所以我回去,给了一个提示,以我们之前看到的那种前缀形式,提示我们应该尝试对什么进行归纳,仍然将证明的其余部分留给 Proofster 完成。这一次,Proofster 能够正确地合成一个证明。主要的启示是:证明搜索树的交互式可视化可以帮助程序员理解搜索失败的原因。

因此,未来进一步研究机器学习方法的可视化以及对失败案例的分析可能会非常富有成果。

总结与要点 📝

本次讲座的主要要点是:

  • 定理证明对于形式化数学和软件验证非常重要。
  • 反过来,机器学习和人工智能对于定理证明也很重要,反之亦然。
  • 在证明生成方面仍有许多改进空间。我在这里省略了很多结果,因为就目前的能力而言,它们并不十分令人印象深刻,这为进入该领域的新人可能取得的成就留下了很大空间。
  • 关于整个流程,仍有许多方面需要考虑改进。
  • 存在许多开放性问题,因此在这个领域工作有很多机会。

以下是一些资源,如果有人认为这是一个有趣的空间可以进入,可以查看:

  • 工具:Lean Copilot, Proofster。
  • 入门材料:学习如何实际使用证明助手,因为这对于为它们开发机器学习工具是必要的。
  • 综述论文:现在有一篇关于深度学习用于定理证明的最新综述论文,并且由于新事物不断涌现,它已经很快变得有些过时了。

好了,如果有人有任何问题,我很乐意回答。谢谢。

012:强化学习基础与算法浅析 🧠

在本节课中,我们将要学习强化学习的基础概念与核心算法。我们将从马尔可夫决策过程开始,逐步了解如何通过试错和反馈来学习并改进策略。课程内容旨在为初学者提供一个清晰、直观的入门指南。


马尔可夫决策过程基础

上一节我们介绍了课程概述,本节中我们来看看强化学习的核心数学模型——马尔可夫决策过程。

强化学习研究的许多问题本质上是动态系统。当你与之交互时,系统状态会因你的行动而改变,你需要考虑行动带来的长期影响。为了对此建模,强化学习领域主要研究马尔可夫决策过程

在MDP中,我们假设在每个时间步 t,系统处于一个状态 s_t。智能体采取一个行动 a_t。随后,智能体可能会收到一个依赖于状态和行动的奖励 r_t,并观察到系统转移到下一个状态 s_{t+1}

智能体的目标是,根据当前状态 s_t,构建一个策略 π,该策略将状态映射到行动。我们的目标是找到一个能最大化长期回报的策略。

长期回报通过折扣因子 γ 来量化,它决定了智能体对未来奖励的重视程度。回报 G_t 定义为从当前状态开始,未来所有折扣奖励的期望和:

公式: G_t = E[ Σ_{k=0}^{∞} γ^k * r_{t+k} ]

折扣因子 γ (0 < γ < 1) 激励智能体高效地达成目标,而不是徘徊不前。


已知模型下的优化:动态规划

上一节我们定义了MDP和优化目标,本节中我们来看看当已知环境模型(即奖励函数和转移概率)时,如何计算最优策略。

首先,我们定义一个关键工具:状态-行动价值函数,或称 Q函数Q^π(s, a) 表示在状态 s 下采取行动 a,然后遵循策略 π 所能获得的期望回报。

贝尔曼证明了最优策略的Q函数满足贝尔曼最优性方程

公式: Q*(s, a) = r(s, a) + γ * Σ_{s'} P(s'|s, a) * max_{a'} Q*(s', a')

这个方程是递归的,为我们提供了计算最优值函数的两种主要算法思路。

以下是两种基于动态规划的经典算法:

  • 价值迭代:直接迭代更新状态的价值函数 V(s),使其逼近最优值。在每次迭代中,对每个状态 s 进行如下更新:
    公式: V_{k+1}(s) = max_a [ r(s, a) + γ * Σ_{s'} P(s'|s, a) * V_k(s') ]
    迭代收敛后,最优策略即在每个状态选择能最大化 Q值 的行动。

  • 策略迭代:交替进行两个步骤。

    1. 策略评估:给定一个策略 π,计算其Q函数 Q^π
    2. 策略改进:根据计算出的Q函数,更新策略为在每个状态选择 argmax_a Q^π(s, a)
      重复这两个步骤,策略会收敛到最优策略。

从已知到未知:基于样本的学习

上一节我们讨论了在已知模型时如何求解,本节中我们进入强化学习的核心挑战:在未知环境模型的情况下,如何通过试错收集的样本来学习和优化策略。

我们首先需要解决两个问题:1) 如何评估一个未知环境下的策略?2) 评估后如何改进策略?

策略评估方法

评估策略价值的主要方法有两种:

  • 蒙特卡洛方法:通过运行完整回合(episode)来收集轨迹 (s_0, a_0, r_0, s_1, a_1, r_1, ...)。对于轨迹中访问过的每个状态 s_t,其回报 G_t 可以直接从后续观测到的奖励计算得出(G_t = r_t + γ r_{t+1} + γ^2 r_{t+2} + ...)。然后用 G_t 作为真实回报的样本,来更新状态价值估计 V(s_t)
    优点:无偏估计。
    缺点:必须等待回合结束才能更新,且需要回合式环境。

  • 时序差分学习:无需等待回合结束,在每一步都可以更新。其核心思想是使用当前的估计值来“引导”。TD更新公式为:
    公式: V(s_t) ← V(s_t) + α * [ r_t + γ * V(s_{t+1}) - V(s_t) ]
    其中 r_t + γ * V(s_{t+1}) 被称为 TD目标- V(s_t)TD误差
    优点:可以在线更新,更高效,适用于非回合式环境。
    缺点:因为使用了估计值 V(s_{t+1}),所以是有偏估计,但方差通常更低。


经典算法:Sarsa 与 Q-Learning

上一节我们学会了如何评估策略,本节中我们来看看如何将评估与改进结合,形成完整的强化学习算法。

以下是两种经典的TD控制算法:

  • Sarsa (On-policy):算法得名于其更新所需的数据 (s_t, a_t, r_t, s_{t+1}, a_{t+1})。它遵循一个探索性策略(如ε-贪心策略)来收集数据,并用这些数据来评估和改进同一个策略。
    更新公式: Q(s_t, a_t) ← Q(s_t, a_t) + α * [ r_t + γ * Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t) ]
    注意,a_{t+1} 是根据当前策略在 s_{t+1} 下采样得到的行动。

  • Q-Learning (Off-policy):这是更著名且强大的算法。它同样使用ε-贪心策略来探索环境,但在更新Q值时,它假设下一步会采取当前估计下的最优行动,而不是策略实际采样的行动。
    更新公式: Q(s_t, a_t) ← Q(s_t, a_t) + α * [ r_t + γ * max_{a'} Q(s_{t+1}, a') - Q(s_t, a_t) ]
    Q-Learning是离策略的,因为它用来更新Q值的目标策略(贪心策略)与用来探索的行为策略(ε-贪心策略)不同。这使得它可以更有效地利用历史数据,也是其成功应用于深度强化学习的关键之一。


价值函数近似与深度Q网络

上一节我们介绍了表格型算法,本节中我们来看看当状态空间巨大或连续时,如何利用函数近似(如神经网络)来推广Q-Learning。

当状态空间很大时,我们无法为每个状态-行动对维护一个Q值表。解决方案是用一个参数化函数 Q(s, a; θ) 来近似Q函数,其中 θ 是参数(如神经网络的权重)。目标是最小化预测Q值与目标Q值之间的误差。

深度Q网络的核心思想是结合Q-Learning和神经网络。它引入了一个关键技巧:目标网络

  • 主网络 Q(s, a; θ):负责生成行动并不断更新。
  • 目标网络 Q(s, a; θ^-):用于计算TD目标 r + γ * max_{a'} Q(s', a'; θ^-)。目标网络的参数 θ^- 定期从主网络复制过来,而不是每一步都更新。
    这种“固定目标”的做法极大地提高了训练的稳定性。

代码思路(伪代码):

初始化主网络 Q(θ) 和目标网络 Q(θ^-) (令 θ^- = θ)
对于每个回合:
    初始化状态 s
    当回合未结束:
        根据 Q(θ) 使用ε-贪心策略选择行动 a
        执行 a,观察奖励 r 和下一个状态 s'
        将经验 (s, a, r, s') 存入回放缓冲区
        从缓冲区采样一批经验
        对于每个样本:
            # 使用目标网络计算目标
            如果 s‘ 是终止状态: target = r
            否则: target = r + γ * max_{a'} Q(s', a'; θ^-)
            计算损失 L = (target - Q(s, a; θ))^2
        对主网络参数 θ 执行梯度下降,最小化 L
        每隔C步,更新目标网络:θ^- ← θ

策略梯度方法

上一节我们学习了基于价值的方法,本节中我们来看看另一类直接优化策略的方法——策略梯度。

与Q-Learning学习价值函数再推导策略不同,策略梯度方法直接参数化策略 π(a|s; θ),并通过梯度上升来优化期望回报 J(θ)

策略梯度定理给出了目标函数 J(θ) 梯度的优雅表达式:
公式: ∇_θ J(θ) = E_π[ G_t * ∇_θ log π(a_t|s_t; θ) ]

这意味着,我们可以通过采样轨迹,计算每个时间步的回报 G_t,然后乘以对应动作概率的对数的梯度,来获得梯度的无偏估计。最基础的算法REINFORCE 正是这样做的:

  1. 运行策略 π_θ 采样一个完整轨迹。
  2. 计算轨迹上每个时间步的回报 G_t
  3. 执行梯度更新:θ ← θ + α * Σ_t G_t * ∇_θ log π(a_t|s_t; θ)

策略梯度方法的优势在于其灵活性,可以轻松处理连续行动空间和复杂的策略参数化方式(如深度神经网络)。后续发展出的Actor-Critic方法通过引入价值函数作为基线来降低方差,取得了更大成功。


总结与前沿挑战

本节课中我们一起学习了强化学习的基础知识。我们从马尔可夫决策过程这一核心模型出发,探讨了在已知模型下的动态规划解法。接着,我们深入到强化学习的本质——在未知环境中通过试错学习,介绍了蒙特卡洛和时序差分这两种策略评估方法,并由此引出了Sarsa和Q-Learning这两种经典的控制算法。最后,我们探讨了如何处理大规模状态空间的价值函数近似,以及直接优化策略的策略梯度方法。

强化学习仍然是一个充满活力的研究领域,面临诸多开放挑战,例如:

  • 高效探索:如何超越简单的ε-贪心,引导智能体更智能地探索未知区域?
  • 样本效率:如何用更少的交互样本学习到有效的策略,特别是在现实世界任务中?
  • 稳定性与可复现性:深度强化学习算法的训练常常不稳定,如何保证稳定收敛?
  • 理论理解:对许多现代算法(特别是深度RL)的理论收敛性保证仍不完善。

希望本教程为你打开了强化学习的大门,提供了继续探索这一迷人领域所需的基础概念和直觉。

015:代码生成

在本节课中,我们将要学习代码生成。代码生成是机器学习中一个令人兴奋且具有广泛应用前景的领域。我们将探讨为什么代码生成如此重要,如何评估代码生成模型,以及构建这些模型的核心方法。课程将通过多个案例研究,展示从基础函数生成到复杂仓库级编程的进展,并展望未来的发展方向。

为什么选择代码生成? 💻

上一节我们介绍了课程主题,本节中我们来看看为什么代码生成是一个值得研究的领域。

首先,软件工程是一个价值数千亿美元的全球性产业。这表明代码无处不在,触及我们生活的方方面面,并且具有巨大的经济效用。

代码之所以被称为“代码”,是因为它可以被翻译成可测量、可观察、可执行的结果。这与所有语言的本质类似,但代码的特殊之处在于我们可以立即看到其执行结果。这为机器学习从业者提供了可以利用的信号,例如用于优化、创建数据或过滤数据。

代码是一个模块化、丰富、无限且开放的空间。它从一些核心规则和规律出发,扩展出巨大的复杂性。这种现实世界的属性使我们能够创建知识,因为我们可以发现这些规律并加以利用,将知识迁移到其他任务中。

最后,一个更具科幻色彩但非常令人兴奋的原因是:我们的模型编写代码的能力越强,我们就越能利用它来构建下一代模型。这可能意味着模型直接编写并持续优化自身的源代码,或者仅仅是帮助AI开发者更高效地编写代码、查找文档。

史蒂夫·乔布斯曾说过,每个人都应该学习编程,因为它教你如何思考。这对我们的模型同样适用。学习编写正确的代码迫使你进行正确的推理,进行逻辑思考。

总结来说,代码生成之所以有趣,主要基于其两个特殊属性:

  1. 可执行性:代码可以被执行,我们可以利用执行产生的丰富反馈,用于评估、训练信号、数据生成和过滤。
  2. 多层表示:代码在计算机中有多种表示形式。例如,任何一段代码都可以被翻译成抽象语法树,它描述了代码的核心结构元素。

定义问题空间 🗺️

上一节我们探讨了代码生成的重要性,本节中我们来明确“代码生成”具体指代的任务范围。

代码生成是一个非常宽泛的领域。当人们谈论代码生成时,可能指代许多不同的事情。因此,明确我们讨论的具体任务至关重要。

在软件工程流程中,存在许多不同的步骤。最明显的一步是:我告诉你需要编写什么样的代码(功能规格),然后你将其翻译成代码。但除此之外,还包括调试(从错误的代码修复为通过测试的代码)、文档编写、代码解释、重构、测试生成等。

在本课程中,我们主要讨论的是第一种情况:基于功能规格生成功能正确的代码

以下是功能规格的几种常见形式:

  • 自然语言描述:用一句话描述我们想要的功能,有时可能略带模糊。
  • 结构化描述:例如,给出一个函数签名和文档字符串,说明函数应该做什么,然后要求完成这个函数。
  • 代码翻译:将一种语言的代码翻译成另一种语言。
  • 测试驱动开发:编写一个包含所有期望行为的测试,然后要求生成一个满足这些标准的函数。这种方法有时具有挑战性,因为很难完全写下我们对一个函数的所有期望。
  • 错误修复:修复代码中的错误。此时,功能规格隐含在代码的其余部分以及“某些地方显然出错了”这一事实中。

为了更清晰地界定这个空间,我们可以从以下几个维度来思考:

  • 代码结构:生成单行代码、几行代码、整个函数、类、文件、完整仓库,还是生成一个与现有仓库协同工作的补丁?
  • 编程语言/环境:使用何种编程语言,以及可能依赖哪些库。
  • 正确性定义:如何定义“正确”?通常是代码运行时做了正确的事,但也可能涉及人类偏好或代码风格(例如IDE代码补全的有用性)。
  • 验证方式:如何验证正确性?通过人工判断、测试用例,还是其他方式?
  • 问题规格形式:问题是如何指定的?是自然语言、文档字符串、代码签名,还是包含代码上下文?

在后续的课程中,我们将看到针对这些维度不同组合的案例研究。

评估代码生成模型 📊

上一节我们定义了代码生成的任务空间,本节中我们来看看如何评估模型的性能。

评估是机器学习流程中至关重要的一环。如果你无法定义和测量你所关心的东西,就无法优化它。对于代码评估,有多种判断正确性的方法,但没有一种是完美的。综合使用这些方法,我们可以针对不同类型的代码任务来评估正确性。

代码生成在本质上也是语言生成,因此许多评估方法与通用语言模型评估类似,但也有一些代码特有的优势可以利用。

以下是几种常见的评估方法:

  • 人工判断:人类审查代码是否正确。然而,对于代码审查来说,这是一项非常繁琐的任务,并非所有人都有相应的专业知识、时间或精力。此外,经过RLHF训练的模型生成的代码可能“看起来”很正确,容易误导人工判断。

  • 模型判断:使用另一个模型来审查代码并查找错误。这可以减轻人工的繁琐工作,但判断能力受限于模型自身的知识。

  • 基于参考方案的自动指标:将生成的代码与一个已知正确的参考方案进行比较。

    • BLEU:衡量共同n-gram的权重。在代码中,由于实现方式多样,此方法效果有限。
    • CodeBLEU:扩展了BLEU,通过抽象语法树和数据流注入代码语法信息,进行更基础的比较。
    • BERTScore:比较两个解决方案在Transformer空间中的词嵌入相似度。
    • CodeBERTScore:使用专门为代码训练的模型,并包含自然语言规格,以衡量代码与功能规格的关系。
  • 基于执行的测试用例评估:这可能是代码生成任务的“黄金标准”。我们可以将生成的代码放在沙箱中安全地执行,并针对测试用例运行它,看是通过还是失败。这需要明确的问题陈述(例如,函数名、参数)以确保可靠执行。通常,我们会区分公开测试用例(模型可见)和私有测试用例(用于最终评估,防止模型过拟合)。

研究表明,基于语义匹配的指标(如CodeBLEU)与基于功能正确性的指标(执行测试)之间的相关性很低。

以下是一些常用的基于执行的评估基准示例:

  • HumanEval:最早的自动化基准之一,包含人类编写的问题和测试用例。
  • EvalPlus:通过合成更多测试用例来扩展HumanEval,提高覆盖率。
  • APPS:竞争性编程基准。
  • SWE-bench:评估在完整仓库中定位和修复错误的能力。
  • DS-1000:数据科学任务基准。
  • ClassEval:专注于类生成的基准。
  • LiveCodeBench:持续更新的基准,用于解决训练数据污染问题。

由于代码空间非常具体,一个错误的标记就可能导致解决方案完全失败,因此评估时通常使用 pass@k 指标。即生成k个解决方案,检查其中是否有任何一个通过了测试。k值不同时,可能需要调整采样温度以获得更多样化的样本。

将上述评估方案结合使用通常效果更好。例如,可以先执行测试用例,再让人工检查行为;或者让模型根据执行输出进行自我调试。

构建代码生成模型:标准配方 🧪

上一节我们讨论了如何评估模型,本节中我们来看看构建代码生成模型的标准方法。

构建代码生成大模型的流程与构建通用大模型非常相似,可以看作是一个“标准配方”:

  1. 预训练:在大型代码语料库(如GitHub上的开源代码)上进行预训练。
  2. 指令微调:在较小但高质量的有监督代码数据上进行微调,使模型能够遵循指令。
  3. 基于人类反馈的强化学习:使用RLHF等技术直接纠正模型的错误或塑造其行为。
  4. 推理时方法:模型权重固定后,在推理时采用一些策略来获得最佳行为,例如:
    • 最佳N采样:生成多个样本,然后选择最好的一个。
    • 检索增强生成:根据当前问题,从解决方案数据库中检索相似上下文来辅助生成。
    • 工具使用:在推理时让模型调用外部工具。

虽然流程相似,但代码生成也面临一些特定挑战,并带来特有的机遇:

挑战

  • 需要处理超长上下文以理解整个代码仓库。
  • 需要“填充中间部分”的能力,而不仅仅是自左向右生成。
  • 由于正确解的“悬崖”特性,通常需要生成多个样本。

机遇(主要来自代码执行)

  • 可以利用代码执行信号进行数据过滤。
  • 可以利用代码执行信号进行优化(如强化学习奖励)。
  • 可以利用代码执行进行合成数据生成。
  • 可以在推理时获得反馈(如编译器错误信息)并进行自我调试。
  • 可以优化代码性能(如运行速度、延迟)。

数据与模型专业化 📚

上一节我们介绍了构建模型的整体流程,本节中我们深入探讨其中的关键要素:数据与模型专业化。

构建模型首先需要数据。最明显的数据源是开源代码。例如,GitHub上拥有数亿个开源仓库。我们可以利用这些人类编写的代码来训练模型。

数据的结构可以多样化:

  • 最直接的是纯代码标记序列。
  • 还可以利用GitHub上的元数据,如问题报告、拉取请求等,这些动态信息展示了代码是如何被修改和演进的。

其他数据源包括竞争性编程竞赛、学术材料、编程训练营练习、Stack Overflow问答,以及通过人工专门创建的数据。企业内部的软件代码也是一个潜在的数据源,但通常不易获得。

一个关键问题是:是否应该混合自然语言数据进行训练? 目前的共识是,从一个基础模型开始通常是有益的。因为编写代码,尤其是根据自然语言规格编写代码,需要理解自然语言。自然语言数据中的推理行为可能有助于代码编写。

另一个相关问题是:应该创建一个通用模型,还是专门的代码模型? 目前存在许多专门的代码模型。如果只关心代码任务,将模型容量集中于此往往能获得更好的性能。例如,Code Llama就发布了基础代码模型、代码指令微调模型和专门的Python模型。当然,这取决于具体用例,存在权衡。将代码数据加入通用模型也可能带来反向收益,提升模型在非代码任务上的能力,这印证了“编程教会你如何思考”的观点。

在准备训练数据时,还需要考虑:

  • 语言平衡:不同编程语言的数据可用性差异很大(例如,Python数据远多于Haskell)。平衡编程语言的分布可以显著提高在稀有语言上的性能(平均提升约12% pass@k),尽管可能会略微降低高资源语言的性能。
  • 填充训练:通过特定格式的数据,可以训练模型具备“填充中间部分”的能力,这对于IDE代码补全或仓库级编辑任务非常有用。

案例研究:竞争性编程 🏆

上一节我们讨论了数据和模型选择,本节中我们通过一个具体案例——竞争性编程,来看如何应用这些方法。

竞争性编程比HumanEval这类基础函数生成任务更进一步。它仍然有明确定义的问题和可靠的可执行环境,但问题难度范围更广,通常包含时间和内存限制。网站如Codeforces会举办竞赛,参赛者提交解决方案,由隐藏的测试用例进行评判。这为机器学习研究提供了一个良好的外部评估框架。

DeepMind的AlphaCode系统在竞争性编程上取得了显著成功。其系统架构如下:

训练阶段(精调)

  1. 从通用大模型(如Gemini Pro)开始。
  2. 在竞争性编程数据集(CodeContests)上进行多阶段精调。
  3. 使用了一种称为GOLD的离线RL目标。该目标在最大似然损失上增加了一个权重项,使模型更专注于生成那些它本身就有一定概率生成且能通过测试的解决方案,而不是追求覆盖所有可能的正确解。这更符合“只需一个能用的解”的需求。

采样与评估阶段(推理时)
这是系统成功的关键。对于每个问题:

  1. 使用精调后的模型家族生成大量样本(例如100万个)。
  2. 在公开测试用例(以及一些合成测试用例)上执行这些样本。
  3. 根据执行结果对解决方案进行聚类,以获得多样化的候选集。
  4. 使用一个精调过的奖励模型对候选解决方案进行排序,预测它们通过隐藏测试的可能性。
  5. 提交排名靠前的有限数量的解决方案进行最终评判。

这种方法的性能随着采样预算的增加而提升。例如,AlphaCode 2在生成约100万个样本时,在近期竞赛问题上的解决率约为40%,这相当于所有人类参赛者的前87百分位。OpenAI的o1模型也展示了类似趋势,通过大量推理时计算来获得正确解。

然而,这种方法计算成本极高。未来的挑战在于如何以更高效、廉价的方式实现类似能力。

超越人类数据:代码执行驱动的优化 ⚙️

上一节我们看到了在竞争性编程中如何利用大量采样和筛选,本节中我们探索如何利用代码执行信号进行更直接的优化,甚至超越人类水平。

我们可以利用代码的可执行性,在训练和优化中超越仅使用人类编写的数据。以下是几个案例:

  1. AlphaDev:发现更优排序算法

    • 使用强化学习来优化代码的正确性和程序延迟(运行速度)。
    • 将生成汇编代码行设置为动作,将当前程序和CPU状态设置为状态,奖励是正确性和低延迟的累积。
    • 该系统发现了比人类已知更快的排序算法,并已被集成到C++标准库中。
  2. FunSearch:基于冻结模型的函数搜索

    • 使用完全冻结的预训练代码大模型,通过少样本提示来演化解决方案生成程序。
    • 针对一些数学难题(如帽集和装箱问题),这些问题的评估是连续分值。
    • 方法:在提示中展示逐步改进的解决方案示例,利用LLM作为多样性来源生成新解,然后使用可执行的评估函数进行验证和筛选,从而发现新的、更好的解决方案。
  3. PPO-Coder:基于执行的强化学习

    • 训练一个RL智能体来生成代码,其奖励信号包括:编译器反馈(测试用例通过/失败、错误信息)、与参考方案的句法和语义匹配(如通过抽象语法树或数据流图比较),以及KL散度惩罚。
    • 研究发现,在这些奖励中,编译器反馈的贡献相对较弱,但仍有积极影响。这条研究路线展示了如何将代码的多种表示形式和执行反馈整合到优化过程中。

这些案例表明,通过充分利用代码的执行和验证能力,我们可以引导模型发现人类未曾想到的解决方案,或更高效地优化代码性能。

案例研究:仓库级编程 🏗️

上一节我们探讨了针对明确定义问题的优化,本节中我们进入更复杂、更贴近现实的场景:仓库级编程。

在实际开发中,我们通常不是在空白文件中编写独立的函数,而是需要进入一个已有的、可能不够整洁或文档不全的代码库,并对其进行修改以实现目标。这被称为仓库级编程。

2023年提出的SWE-bench基准测试正是针对此类任务。它从GitHub的真实仓库中提取任务实例,将GitHub Issue和Pull Request转化为评估数据集。目标是让模型根据Issue描述,生成一个能通过仓库所有测试的Pull Request。评估指标是仓库内所有测试用例上的可执行正确率。

这是一个非常困难的问题,原因如下:

  • 需要超长上下文:为了理解整个仓库并定位问题,模型需要处理很长的代码上下文。
  • 需要多步推理:错误定位和修复通常像“大海捞针”,需要先定位问题所在,再思考解决方案,这往往需要多步交互。
  • 需要生成补丁格式:模型需要输出diff格式的补丁,这与常见的连续文本生成格式不同,且相关训练数据较少。
  • 数据复杂度高:每个数据点(一个Issue到PR的转换)都非常复杂,导致高质量数据稀缺。

尽管挑战巨大,社区在此基准上取得了快速进展。2023年基准发布时,最佳模型的解决率不到2%。而截至最近,领先模型的解决率已达到约22%。这表明,一旦一个困难的问题被明确定义和评估,就能推动整个社区集中力量取得突破。

未来展望与总结 🚀

在本节课中,我们一起学习了代码生成的多个方面。我们回顾了所介绍的内容,代码生成在过去几年取得了飞速发展,实现了许多曾经的梦想。

模型内部蕴藏着惊人的能力,但降低推理时采样和计算成本将是扩大其影响的关键。无论是AlphaCode系统还是o1模型,初期往往不惜代价证明可能性,随后再致力于知识整合和效率提升。

在SWE-bench等更贴近现实的软件工程任务上,虽然进步显著,但相比人类工程师仍有巨大提升空间。我们还需要更充分地利用代码的特殊属性(如可执行性、多层表示),并将它们深度整合到模型构建的每个环节(训练、优化、推理)中,发挥其全部潜力。

随着代码执行被越来越多地集成到流程中,我们必须时刻牢记执行代码的安全性。需要建立协议,在隔离的沙箱中安全执行代码,并意识到模型生成的代码并不总是正确,甚至可能有害,尤其是在向用户展示代码时。

最后,正如课程开头所言,代码生成将继续帮助我们推动机器学习的进步。希望本节课能作为一个有用的视角,帮助你理解本周所学的一些机器学习经验。

016:现状、应用与挑战

在本节课中,我们将探讨生成式人工智能(AI)如何深刻影响软件工程领域。我们将了解生成式AI在软件开发全生命周期中的潜在应用,并通过近期研究数据,分析当前技术在实际应用中的效果、面临的挑战以及未来的发展方向。


软件工程中的生成式任务无处不在

首先,我们需要理解为什么生成式AI与软件工程如此契合。在标准的软件开发流程中,充满了各种生成式任务。

以下是软件开发中常见的生成式活动:

  • 编写需求:从与利益相关者的沟通中生成正式的需求文档。
  • 生成设计:根据需求生成设计模型和文档。
  • 编写代码:依据需求和设计模型生成可执行代码。
  • 撰写文档:为代码、API或系统生成说明文档。
  • 生成测试:基于代码或需求文档生成测试用例。

这些任务不仅单向进行,还可以在不同阶段之间跳跃和组合。例如,可以从代码反向生成设计模型,或者根据测试用例生成文档。正是由于软件工程中充斥着这类生成式任务,生成式AI才显得特别有吸引力,为我们带来了大量的应用机会和研究挑战。

生成式AI的承诺:自动化与增效

生成式AI的核心承诺在于,它有可能自动化或半自动化许多目前由人工完成的、复杂、耗时甚至重复的软件工程任务。

这适用于贯穿软件全生命周期的各种活动:

  • 需求工程:AI可以基于已有需求,辅助生成或建议新的需求,提升需求获取的效率和完备性。它还能帮助评估需求文档的质量、识别问题,并分析需求变更带来的影响。
  • 代码生成:这是当前最受关注的应用之一。AI可以根据自然语言描述、注释、测试用例等输入,自动生成或补全代码,协助开发人员提高编码效率。
  • 软件测试与质量:AI可以用于自动生成测试用例。在发现软件缺陷(Failure)后,AI还能协助定位导致缺陷的故障(Fault)并自动修复程序。

总而言之,我们正在迈向一个愿景:软件工程师将定期与AI交互,以更高效、更有效的方式完成任务。这正是当前软件工程领域研究的焦点。

现状审视:从实际研究中看效果与挑战

既然这是未来的愿景,那么基于当前可用的技术,我们实际能做到什么程度呢?本节我们将通过一些已发表的研究,揭示生成式AI在软件工程任务中的实际效果和面临的挑战。

开发者实际如何使用AI工具

一项基于GitHub上约2000条提及使用ChatGPT的提交、拉取请求或议题的分析显示,开发者已在多种任务中使用AI工具。

以下是开发者使用AI工具完成的主要任务类型:

  • 实现功能:辅助编写特定功能的代码。
  • 改进开发流程:优化现有的开发过程。
  • 学习解决问题:帮助理解未知技术或解决难题。
  • 操纵数据或环境:处理数据或配置软件环境。
  • 生成测试:自动创建测试用例。
  • 生成文档:编写代码注释、提交描述等文档。

然而,反馈并非全是积极的。报告揭示了成功案例与现存问题。

报告的成功案例包括:

  • 语言翻译:在完成跨编程语言翻译任务时非常有效。
  • 生成文档:在生成代码注释和拉取请求描述等方面被广泛使用,提升了效率。

同时,也暴露了诸多问题和挑战:

  • 代码所有权模糊:开发者可能在不完全理解AI生成代码的情况下将其提交,丧失了对代码的“所有权”和辩护能力。
  • 学习新知识效果有限:当问题涉及较新的框架或库时,由于训练数据滞后,AI往往无法提供正确建议。
  • 推荐过时API:生成的代码可能包含已弃用的API,因为模型是基于包含这些API的历史数据训练的。
  • 产生幻觉(Hallucination):AI可能推荐根本不存在的函数或代码,这需要开发者额外花费精力去甄别和修正。
  • 安全风险:更严重的是,关于不存在软件包的幻觉可能被恶意利用。攻击者可以据此创建包含恶意代码的软件包,一旦该软件包被AI推荐,就可能造成安全威胁。

这些发现引出了第一个重大挑战:如何让AI模型跟上快速发展的技术步伐,并有效应对幻觉带来的安全风险。

AI工具的可用性挑战

另一项针对400多名GitHub贡献者的调查,揭示了AI工具在可用性方面的具体挑战。

以下是当前AI工具在可用性上面临的主要问题:

  • 难以控制:开发者不清楚AI具体参考了项目的哪些部分来生成建议,也难以通过提示词精确控制输出结果(如代码长度、风格),常常需要多次迭代。
  • 交互方式受限:开发者期望更丰富的对话式交互,以更好地控制和调整输出。同时,处理AI生成的多个备选建议也缺乏有效的方法论。
  • 结果难以评估:判断AI生成的推荐是否正确、是否最优非常困难。这导致许多开发者认为,有时自己动手完成任务的成本效益比求助于AI更高。
  • 隐私与版权担忧:开发者担心提示词内容被不当使用,也不确定生成的代码是否涉及版权问题。
  • 缺乏个性化学习:当前工具普遍缺乏根据开发者个人编码风格和工作习惯进行自适应学习的能力。

生成结果的正确性如何?

除了可用性,生成结果的正确性至关重要。然而,研究表明,当前技术在这方面的表现仍不理想。

1. 代码生成任务
在一项针对Java方法生成的研究中,研究者从GitHub选取方法,移除实现部分后让AI(如GitHub Copilot、Bard)补全。结果发现,只有不到一半的生成代码是正确的,且需要开发者投入大量精力检查和修复。对于依赖外部方法(需要更多上下文)的代码,生成效果尤其差。不同AI工具各有优劣,没有哪个工具能完全超越其他。

在另一项更大型的研究中,使用了来自GitHub和LeetCode的共200个方法,并系统测试了不同提示词特征(如是否包含示例、边界情况说明、方法摘要)的影响。结果显示,提示词方式对结果影响很小,仅有“提供摘要”和“提供示例”有轻微统计学意义的影响。更重要的是,代码生成的正确率很低(LeetCode方法约12%,GitHub方法约17%),且这仅是通过了现有测试用例的“正确”,尚未考虑安全性、性能等未测试的问题。

2. 测试生成任务
在单元测试生成方面,有研究训练Transformer模型来根据给定方法生成测试用例。虽然其生成的测试用例可读性更好,更像人工编写,有利于后续维护,但其在代码覆盖率等方面的有效性尚无法与传统的非AI测试生成工具相媲美。一个潜在的思路是结合传统工具生成测试骨架,再用AI提升其可读性。

3. 测试预言(Test Oracle)问题
测试生成中的一个核心难题是“测试预言问题”,即如何为生成的测试用例自动预测预期结果(即断言语句)。生成式AI为解决这一问题带来了希望。已有研究尝试用AI预测断言语句,但正确率不高(一项研究约31%,在更新数据集上甚至降至约10%)。这再次印证了技术快速迭代给AI模型带来的挑战。


总结与未来展望

本节课我们一起学习了生成式人工智能在软件工程领域的应用与挑战。

总结来说,生成式AI确实正在成为软件工程领域的“游戏规则改变者”。它提供了自动化众多复杂任务的潜力,这在学术界和工业界都引发了广泛的研究和应用热潮。

然而,我们也面临着严峻的挑战:

  1. 技术迭代速度:AI模型的训练速度难以跟上软件技术、框架和库的快速更新。
  2. 幻觉与安全:模型可能生成不准确或不存在的内容,带来潜在的安全风险。
  3. 可控性与可用性:如何让开发者有效控制AI的输入、行为和输出,并设计出高效的人机交互模式,仍是未解之谜。
  4. 隐私与版权:需要妥善处理提示词隐私和生成内容的版权问题。
  5. 个性化适应:工具需要能够学习并适应不同开发者的风格和偏好。
  6. 实际有效性:当前模型在代码生成、测试生成等任务上的正确率和可靠性尚未达到可完全信赖并交付生产的水平。

展望未来,我们很可能需要设计新型的人机协同模式,让开发者和AI进行有效的多轮对话与合作。更进一步,未来的开发团队可能由人类开发者和具备不同能力的AI智能体混合组成,他们如何高效协作,将是一个激动人心的研究方向。

017:图神经网络

概述

在本节课中,我们将要学习图神经网络。我们将从理解图的基本概念开始,探讨为什么图结构在机器学习中非常重要,然后深入讲解图神经网络的核心原理、不同类型以及它们在实际中的应用和挑战。

为什么需要关注图?

在开始之前,我们先要明确讨论的对象。我们讨论的不是图表或曲线图,而是由节点和边构成的网络图。理解图结构之所以重要,是因为现实世界中的许多系统,从社交网络到分子结构,都可以自然地表示为图。图为我们提供了一种强大的方式来建模实体之间的复杂关系和交互。

上一节我们介绍了图的基本概念,本节中我们来看看如何用数学语言描述一个图。

图的表示方法

一个图由两个基本部分组成:节点和边。节点代表系统中的实体,边代表实体之间的连接。

  • 节点特征矩阵:我们可以为每个节点分配一个特征向量来描述其属性。所有节点的特征可以组合成一个特征矩阵 X,其中每一行对应一个节点的特征。
  • 邻接矩阵:图的结构通常用一个方阵 A 来表示,称为邻接矩阵。如果节点 i 和节点 j 之间存在一条边,则 A[i, j] = 1,否则为 0。

根据边的属性,图可以分为几种主要类型:

  • 无向图:边没有方向,邻接矩阵是对称的。
  • 有向图:边有方向,邻接矩阵通常不对称。
  • 加权图:边带有权重,邻接矩阵中的值表示连接强度。

除了这些基本类型,图还有许多其他分类,例如连通图、完全图、二分图、树、环图等。图的一个关键特性在于它为数据引入了结构,并定义了我们可以提问和求解的问题域。

几何深度学习与对称性

在深入图神经网络之前,我们需要理解一个更广泛的框架:几何深度学习。其核心思想是,神经网络是一个高度参数化的函数,它将定义在某个域上的输入信号映射到输出。

以识别图片中的数字“3”为例:

  • :是像素组成的规则网格。
  • 信号:是网格上显示的“3”这个图案。
  • 目标:是让神经网络函数输出标签“3”。

关键在于,我们可以利用域中存在的对称性来帮助模型做出更好的预测。例如,无论数字“3”在图片中如何平移,我们都希望模型能正确识别它。这引出了两类重要的函数:

  • 不变函数:当输入经历某种变换(如平移)时,函数的输出保持不变。图像分类任务需要这种函数。
  • 等变函数:当输入经历变换时,输出也以可预测的方式发生相应变化。例如,目标检测任务中,目标位置的变化会导致检测框位置发生相应变化。

对于图像(网格域),关键的对称群是平移。而对于图,其关键的对称群是节点排列。图神经网络正是为处理这种排列对称性而设计的架构。

图同构与WL测试

理解排列对称性,需要探讨图同构问题:我们如何判断两个图在结构上是否相同(即仅通过重标节点就能使它们完全一致)?

判断图同构的一个经典方法是Weisfeiler-Lehman测试。以下是WL测试的步骤:

  1. 为每个节点初始化一个标签(例如,基于其度数)。
  2. 对于每个节点,收集其所有邻居的标签,排序后与自身标签结合,生成一个新的聚合标签(通常使用哈希函数)。
  3. 重复步骤2,直到节点的标签划分不再变化。
  4. 比较两个图的最终标签分布。如果分布不同,则两个图一定不同构;如果分布相同,则它们可能同构(WL测试无法做出绝对判断)。

WL测试的重要性在于,它从理论上界定了一般图神经网络的表达能力上限。

图神经网络的核心:消息传递

图神经网络的基本构建模块是消息传递。每一层消息传递都包含三个步骤,用于更新节点的表示(嵌入):

以下是消息传递的步骤:

  1. 生成消息:对于每条边(连接的两个节点),根据源节点和目标节点的当前特征生成一条消息。
    • 消息函数可以是任何函数,它定义了不同GNN架构的特性。
  2. 聚合消息:对于每个节点,将其从所有邻居节点收到的消息聚合起来。
    • 聚合函数必须是排列不变的,即不依赖于邻居节点的顺序。常用操作包括求和、求平均或取最大值。
  3. 更新节点:结合节点自身的特征和聚合后的邻居消息,更新该节点的特征表示。

通过堆叠多层消息传递层,节点可以聚合来自越来越远邻居的信息。这使模型能够捕获图的全局结构。

经典图神经网络模型

基于消息传递框架,可以衍生出多种具体的图神经网络模型。以下是几种经典模型:

  • 图卷积网络:一种早期的GNN模型。其消息传递步骤可以简化为一个矩阵运算:H' = σ(Â H W),其中 Â 是添加了自连接的归一化邻接矩阵,H 是节点特征矩阵,W 是可学习的权重矩阵。
  • GraphSAGE:引入了归纳学习能力,意味着它可以在一个图上训练,然后泛化到未知的新图上。其核心是采样和聚合:对于每个节点,先采样固定数量的邻居,然后学习一个聚合函数来整合这些邻居的信息。
  • 图注意力网络:在聚合步骤中引入了注意力机制。每个节点可以学习为其不同的邻居分配不同的重要性权重,从而进行更有针对性的信息聚合。这通常通过多头注意力来实现,以稳定学习过程并捕获不同方面的信息。

除了上述模型,还有许多其他变体,如专门研究图同构表达能力的图同构网络,处理高阶结构的单纯复形GNN,以及用于异质图(包含多种节点和边类型)的异质图神经网络

图神经网络的挑战

在实际应用中,图神经网络面临一些主要挑战:

  • 过度平滑:当堆叠层数过多时,所有节点的特征会变得过于相似,丢失了区分性。这是因为消息传递过多导致节点特征“平均化”。
    • 解决方案:使用更浅的网络、添加残差连接、进行边丢弃、或使用特定的归一化技术(如PairNorm)。
  • 过度挤压:当图中存在信息瓶颈(如连接两个稠密子图的少数节点)时,远距离节点间的信息难以有效传递,因为需要压缩通过瓶颈的信息过多。
    • 解决方案:使用更宽的聚合函数(如多头注意力)、修改图结构(添加远程连接)、或使用扩展图传播(如K阶图)来改善远程信息流。

图神经网络的应用

图神经网络具有极其广泛的应用前景,因为任何能建模为实体和关系的系统都可以用图来表示。以下是一些典型应用领域:

  • 分子性质预测:用于药物发现,预测分子的化学、物理或毒性属性。
  • 知识图谱补全:推断大型知识图谱中缺失的事实或关系。
  • 社交网络分析:进行社区发现、影响力预测或异常检测。
  • 推荐系统:利用用户-物品二分图进行个性化推荐。
  • 3D形状识别:将点云或网格数据视为图进行处理。
  • 交通网络预测:预测道路网络中的流量或拥堵情况。

总结

本节课中我们一起学习了图神经网络。我们从图的基本表示和重要性出发,探讨了几何深度学习中对称性的核心概念,并深入讲解了图神经网络的基础——消息传递框架。我们介绍了几种经典的GNN模型(如GCN、GraphSAGE、GAT),分析了它们面临的主要挑战(过度平滑和过度挤压),并列举了GNN在多个领域的强大应用。图神经网络为我们提供了一种强大的工具,用于理解和预测具有复杂关系结构的数据。

018:模仿学习与强化学习

在本节课中,我们将要学习机器人学习中的两大核心范式:模仿学习与强化学习。我们将探讨它们的基本概念、面临的挑战、常用算法以及如何在实际机器人系统中应用。

概述

获取通用智能需要为世界建模,这非常困难。环境和任务可能变化,现实世界中的环境通常是非平稳的。策略结构可能受限,我们使用的优化求解器可能出错或陷入局部最优。我们的假设可能不成立,或仅在非常特定的领域成立。在机器人学中,我们通常试图解决感知、规划与控制(或称感知、思考、行动)这三元问题,传统基于模型的方法将它们分开处理。但要实现智能,我们需要一个能够真正将这些部分整合在一起的系统和算法,以实现反应性、鲁棒性和自适应性。这就是机器人学习希望达成的目标。

上一节我们介绍了机器人学习的总体目标,本节中我们来看看具体的学习方法及其数学基础。

符号与基础概念

首先,我们来看一些后续将用到的符号和概念。

你现在已经知道什么是马尔可夫决策过程(MDP)。我们有一个状态空间 S,一个动作空间 A,可能还有一个观测空间 O(尤其是在我们无法访问环境真实状态的情况下)。我们有一个状态转移概率矩阵 P(s'|s, a),可能有一个观测模型 O(o|s) 和一个奖励函数 R(s, a)。在严格的马尔可夫决策过程中,我们只有状态、动作、转移和奖励这个元组,当然还有初始分布等。我们的目标是学习一个策略或控制器,使其在给定状态下给出最优动作。

但在机器人学中,我们通常处理的是部分可观测马尔可夫决策过程(POMDP)。这时观测和观测模型就会出现,因为我们只能观测到环境的一部分,需要推断出真实状态。有不同的技术可以处理这个问题,例如使用粒子滤波等滤波技术来获得对状态的信念,或者简单地将历史信息输入神经网络策略,让网络理解过去发生了什么,以便推断未来,进而推断策略中的动作。

但整个思想基于马尔可夫假设,即给定当前状态,未来与过去独立。简单来说,马尔可夫假设认为当前状态是充分统计量,因此你不需要历史信息。不过,如前所述,在处理部分可观测环境时,我们可能会使用历史信息作为技巧,将POMDP问题转化为MDP问题,并用我们已有的算法(例如强化学习算法)来解决。

模仿学习

接下来,我们将重点讨论模仿学习。在模仿学习中,我们面临一些问题,因为马尔可夫性在那里并不真正成立。

模仿学习的主要思想是模仿专家数据。因此,你首先需要收集这些专家数据,它们可能是观测值或状态(取决于你的环境)以及一些动作对。然后,你需要训练一个函数,将这些观测值或状态映射到你收集到的那些动作上。

以下是模仿学习的一个核心方法列表:

  • 行为克隆:这是最基本的方法,试图显式地将观测或状态映射到专家给出的动作。
  • 逆强化学习:试图基于演示学习人类的成本函数或奖励函数,然后提取策略。
  • 生成对抗模仿学习:使用生成对抗网络框架,让生成器(策略)生成轨迹,判别器区分轨迹来自专家还是生成器,两者相互竞争优化。

行为克隆及其挑战

行为克隆的核心是使用监督学习来匹配专家的动作。例如,在自动驾驶中,我们收集来自传感器的数据(如摄像头图像)和来自方向盘等执行器的驾驶动作,然后训练神经网络进行映射。

然而,行为克隆并非标准的监督学习。在标准的监督学习(如分类)中,我们假设数据是独立同分布的(IID)。理论告诉我们,当数据和目标来自某个分布且是IID时,训练误差越低,测试性能越好。

但在行为克隆中,数据并非IID。我们所看到的和我们所做的之间通常存在因果关系,这影响了一切。我们还有其他问题,例如专家数据只包含良好行为,我们从未见过不良行为,因此不知道如何应对它们。因此,测试期间的任何小错误都会导致策略偏离专家分布,产生级联故障。在行为克隆中,低训练误差并不一定能转化为良好的测试性能,因为最初的小数值误差会不断累积,导致你偏离并出现分布偏移。

在顺序决策中,分布偏移问题比分类等问题更严重,因为它可能是致命的,并且是动态的。问题在于,策略只训练在专家的最佳数据上,专家几乎看不到坏状态,因为他们是专家。当我们进行行为克隆时,我们通常希望克隆好的行为,而不是坏的行为。一般来说,模仿学习就像在悬崖边行走,一旦你偏离好的轨迹一小步,就可能跌落悬崖。一旦犯错,你就超出了训练分布,出现了分布外偏移。

改进行为克隆的方法

为了让模仿学习和行为克隆发挥作用,我们需要不同类型的数据。例如,我们也需要包含更多错误或尝试纠正错误的数据。

以下是两种主要的改进方法:

  1. 数据增强:第一种方法是故意在轨迹中添加错误并添加对这些错误的纠正。第二种方法是使用合成或伪造的数据来增强数据,以说明纠正措施。
  2. DAgger算法:数据聚合算法的主要思想是,为了纠正训练过程中发生的错误,你需要更多数据,而最好的数据提供者就是人类专家。因此,DAgger本质上是一个人在环的学习过程。

尽管DAgger是一个已有13年历史的算法,但它仍然被使用。我们仍然基本上依赖人类来收集更高质量的数据,尤其是在进行模仿学习时。这是许多大型科技公司的基本策略,他们试图构建大规模的行为克隆模型,让人在分布外时明确提供新数据,不断训练直到获得非常好的行为。

深度模仿学习与生成模型

显然,当神经网络出现后,我们的生活变得更美好了,因为它们有能力建模更复杂的函数和交互。

目前,我们进行模仿学习的方式是使用生成模型。我们尝试用机器学习中当前流行的每一种生成模型来进行模仿学习。生成模型试图学习一个分布 p_θ(x) 以匹配数据的分布。一旦学习了这个生成模型,你就可以从中采样,潜在地获得数据集中未明确见过的新数据。这非常强大,也是我们喜欢它相比于更监督式的策略学习方式的原因。

我们使用了各种类型的生成模型:生成对抗网络、变分自编码器、扩散模型以及归一化流。

一个重要的生成模型模仿学习算法是生成对抗模仿学习方法。在这个方法中,你从学生策略中采样轨迹,然后有一个判别器。生成器模型生成样本,判别器试图区分这些样本是来自专家数据还是伪造的,它们相互竞争,因为生成器试图让判别器相信数据是真实的。

然而,GAN的一个问题是它通常无法捕捉多模态行为,答案是否定的,因为它通常会坍缩到特定的模式。变分自编码器也存在同样的问题。

目前,模仿学习乃至更广领域的巨大热潮是使用扩散模型进行模仿学习。扩散模型有一个前向加噪过程,然后我们进行反向扩散以去除这些噪声。这正是我们试图学习的函数——去噪函数。

扩散策略论文研究了使用扩散模型来学习直接应用于机器人的动作。与原始扩散模型的不同之处在于,现在输入不仅只有图像,还有动作。然后你运行推理K次,这取决于你想要扩散多少步。

从模仿学习到强化学习

然而,模仿学习是不够的。特别是对于机器人学来说,它是不够的,因为世界是巨大的。你需要专家、人类来收集这些数据,这非常非常困难。即使你需要通过强化学习来创建专家,也需要知道如何为每种可能的情况创建环境、奖励函数等的人。现实世界是非常不确定的、非平稳的,并且让人一直在环中以进行判断和提供新数据是相当昂贵的。这些数据对于超级智能来说是不够的。

理想情况下,我们希望这些具身AI系统能够自主学习。强化学习的目标是最大化累积折扣奖励(我们称之为回报),并从中提取最优策略以及如何行动。

深度Q学习

深度Q学习尝试使用深度神经网络作为函数逼近器来学习如何行动。DQN于大约11年前由DeepMind提出,并在Atari游戏中展示了超人的性能。

与表格Q学习相比,深度Q网络的输入通常是图像(例如四帧),输出是离散动作。网络接收四帧图像,通过一些卷积层处理这些图像,然后通过一些全连接层解码出每个可能动作的Q值。你选择具有最大Q值的动作。

在机器人学中,Q学习可以应用于“桌面”环境,即只有一个顶部摄像头,你创建动作价值图。你假设机器人会改变图像中的像素状态。例如,如果机器人移动一个盒子,盒子会移动,图像中的像素就会改变。这被认为是一个动作。另一个广泛使用Q学习的用例是抓取。如果你将其框定在图像空间内,当你选择具有最大抓取价值的东西时,你会改变图像,改变图像的状态,因为你会移除某些东西。这是一个离散化的空间,因为你的图像有特定的大小,每个像素都是一个可能的动作。

演员-评论家方法与连续控制

但在高自由度机器人中,我们需要进行连续控制。这时我们就需要使用演员-评论家方法,它允许我们训练连续动作的策略,并真正控制可以取连续值的机器人关节。

演员-评论家方法中,我们既估计Q函数,也学习一个策略,并尝试使用学习的Q函数持续改进这个策略。

一个对机器人学产生重大影响的算法是软演员-评论家算法。它是演员-评论家框架,演员是策略网络,评论家是评估动作好坏的Q函数。SAC的重要之处在于它有一个软目标,允许该算法以最大熵进行探索。它试图在过程开始时做许多随机动作,以尽可能探索环境,然后尝试更新策略,但它总是通过这个软目标进行正则化,以平衡探索与利用。它给你一个随机策略,这在某些情况下很好,例如当你想逃离局部极小值时。

另一个重要算法是近端策略优化。它基于优势函数训练策略。优势是价值函数与Q函数之间的差值。PPO试图通过不改变太多相对于先前策略的方式来更新策略,它引入了一些裁剪方法,以防止在更新策略参数时做出太大的更新,并非常有效地权衡探索与利用。PPO仍然与机器人学非常相关,特别是在Isaac Gym等强大模拟器的支持下,它能够并行训练数千个机器人。

总结

本节课中我们一起学习了机器人学习的两大支柱:模仿学习与强化学习。

我们首先探讨了模仿学习,特别是行为克隆,它通过监督学习模仿专家数据,但面临分布偏移和误差累积的挑战。改进方法包括数据增强和DAgger等人机交互算法。随后,我们介绍了使用生成模型(如GAN、VAE和扩散模型)的现代模仿学习方法,它们能更好地处理多模态行为并生成新数据。

接着,我们转向强化学习,它让智能体通过试错与环境交互来学习最优策略。我们介绍了深度Q学习用于离散动作空间,以及演员-评论家方法(如SAC和PPO)用于机器人中更常见的连续控制问题。这些算法结合了值函数估计和策略梯度,能在复杂模拟环境中有效训练机器人技能。

最后,我们看到了当前的前沿方向,例如结合强化学习与扩散模型来学习丰富的行为模式。机器人学习的未来在于结合模仿学习的效率与强化学习的自主探索能力,并利用越来越逼真的模拟器来加速训练和实现向现实世界的迁移。

posted @ 2026-03-26 12:21  布客飞龙III  阅读(4)  评论(0)    收藏  举报