USC-CSCE585-机器学习系统笔记-全-
USC CSCE585 机器学习系统笔记(全)
1.1:感知机与逻辑回归 🧠

在本节课中,我们将学习机器学习的基础概念,特别是深度学习的基石。我们将从最简单的线性分类器——感知机开始,理解其工作原理和背后的数学原理,并探讨如何通过学习过程来调整模型参数,使其能够正确地对数据进行分类。
从输入到决策
上一节我们介绍了课程的整体目标。本节中,我们来看看一个分类器的基本工作流程。
一个分类器通常接收一个输入 X(例如一封邮件或一张图片),并将其转换成一个特征向量。这个特征向量是一个数学上的向量,其中每个位置代表一个特定的特征值。然后,模型基于这个特征向量做出一个决策(例如,判断邮件是否为垃圾邮件)。
以下是两个关键过程:
- 特征提取:将原始输入转换为特征向量。在传统机器学习中,特征通常是人工设计的;而在神经网络中,特征是从数据中自动学习得到的。
- 决策:将特征向量通过某种“魔法”映射到一个决策(例如,分类标签)。本课程的重点将放在这个过程上,我们假设特征向量是已知的。
生物神经元与数学模型的联系
理解了分类流程后,我们来看看其灵感来源。感知机的设计灵感来源于我们大脑中的生物神经元。
一个神经元通过树突接收来自其他神经元或感官的输入信号。每个连接有不同的强度(或权重)。细胞核(胞体)对这些加权输入进行汇总,并决定是否通过轴突向其他神经元发送输出信号。
我们可以用数学来模拟这个过程:
- 输入信号表示为特征值
F。 - 连接强度表示为权重
W。 - 细胞核的汇总计算就是计算加权和,即激活值
a,其公式为:
a = W · F(点积运算)
在这个模型中,特征值 F 是给定的,我们无法改变。学习的目标就是找到一组正确的权重 W,使得模型能根据激活值 a 做出正确的决策。对于一个二分类器,一个简单的决策规则是:如果 a > 0,则预测为正类;如果 a < 0,则预测为负类。
权重向量与决策边界
知道了模型如何决策,我们再来看看权重向量的几何意义。
权重向量 W 和特征向量 F 都存在于一个高维空间中。W 不仅决定了激活值的正负,还定义了一个决策边界(一个超平面)。在二维空间中,这个边界就是一条直线。
- 位于决策边界一侧的点会被分类为正类。
- 位于另一侧的点则被分类为负类。
学习的过程,本质上就是寻找一个能最好地将不同类别的数据点分开的决策边界。对于线性模型,这个边界是直线(或超平面);对于更复杂的模型(如神经网络),决策边界可以是非线性的。
感知机学习算法
那么,如何通过数据来“学习”或调整权重向量 W 呢?这就是感知机学习算法的核心。
这是一个迭代更新过程,其核心思想是:如果模型对当前样本的分类是错误的,就调整权重向量,使其更倾向于做出正确的分类。
以下是算法的具体步骤:
- 初始化:将权重向量
W初始化为零(或小的随机值)。 - 遍历数据:对于训练数据集中的每一个样本
(F, y*),其中F是特征向量,y*是真实的标签(例如,+1 或 -1)。 - 做出预测:用当前权重计算激活值并得到模型预测的标签
y。规则为:若W·F > 0,则y = +1;否则y = -1。 - 比较与更新:
- 如果预测正确(
y == y*),则保持W不变。 - 如果预测错误(
y != y*),则按以下规则更新权重:
W = W + y* · F
这个更新意味着:将权重向量W朝着(或远离)特征向量F的方向移动,以修正本次错误。
- 如果预测正确(
为了更直观地理解更新过程,我们可以看一个例子。假设真实标签 y* = -1,但模型预测 y = +1(即 W·F > 0)。根据更新公式 W = W + (-1)·F,我们实际上是从 W 中减去了 F。这使得新的 W 与当前 F 的点积 (W - F)·F 会变小,从而更可能在未来对此样本做出负类的正确预测。
这个过程会对所有训练样本重复多轮,直到所有样本都被正确分类,或达到预设的迭代次数。如果数据是线性可分的,该算法保证能够找到一个解。
总结
本节课中我们一起学习了机器学习的基础构件。
- 我们了解了分类器从输入到决策的基本流程,区分了特征提取与决策两个阶段。
- 我们探讨了感知机模型如何受生物神经元启发,并用数学公式
a = W·F描述其核心计算。 - 我们认识了权重向量的几何意义,即它定义了特征空间中的决策边界。
- 最后,我们详细剖析了感知机学习算法,理解了它如何通过错误驱动的权重更新(
W = W + y* · F)来从数据中学习。

这为我们接下来学习更复杂的分类器(如多类分类、逻辑回归和神经网络)奠定了坚实的基础。下一节,我们将探讨当类别不止两个时,应如何扩展这些概念。
2.1:为机器学习设计计算机系统 🖥️

在本节课中,我们将要学习如何为机器学习系统进行设计。我们将探讨构建一个可投入实际使用的机器学习系统与在本地进行模型研究之间的关键区别,并了解其中涉及的核心组件、挑战和必要的工具。
概述
上一讲我们讨论了算法偏差、鲁棒性以及人工智能与计算的关系。本节中,我们来看看构建机器学习系统的设计过程。我们将了解到,一个完整的机器学习系统远不止是模型代码,它还包括数据处理、部署、监控等多个关键环节。
系统设计的重要性
构建任何系统,无论是软件系统、物理系统还是化学应用,如果没有一个工程化的设计流程,都将是非系统性的,并会带来诸多问题。机器学习系统也不例外。虽然你可以将机器学习系统视为一种软件系统,但它与非机器学习系统存在本质区别。
实践中的挑战
为了理解设计机器学习系统的难点,我们可以参考行业实践者的调查。以下是他们指出的几个关键方面:
- 问题分解:将开放式问题分解为简单的组件或基础构建模块至关重要。需要识别哪些模块需要机器学习,哪些则不需要。
- 关注范围:许多从业者非常了解模型类别(如线性模型、决策树),但对数据清洗、数据准备、日志记录、评估指标、推理(模型部署)、特征存储等其他系统方面了解不足。
- 问题本质:定义问题、识别相关指标、深刻理解机器学习能做什么和不能做什么是关键。机器学习方法可能每年都在变化,但解决问题的方式是相对稳定的。
机器学习系统的技术深度
一项来自谷歌的重要研究揭示了机器学习系统中隐藏的技术深度。研究表明,对于任何机器学习系统,都包含许多不同的组件。人们通常谈论和了解的“ML代码”(即训练模型和进行预测的部分)实际上只是整个系统的一小部分。
系统还包括数据收集、数据验证、资源管理、模型服务、监控、分析、流程管理、特征提取和配置等组件。这些组件与ML代码本身同等关键,甚至更为重要。
研究还指出了机器学习系统积累的一些重要技术深度,例如:
- 数据依赖性
- 模型复杂性
- 模型的可复现性
- 系统测试方面
- 监控
- 配置问题
- 外部变化
如果没有系统化的流程和合适的工具来处理这些问题,系统将无法扩展。
系统栈与生产流程
当我们谈论“系统”时,指的是软件、硬件和中间件的组合。这包括硬件层(如CPU、内存、GPU)、操作系统内核(中间件)以及软件层(为手头任务编写的代码,如视频编码器、数据库等)。
更具体地说,当讨论投入生产时,我们需要一个流程。这个流程通常始于数据准备。对于任何涉及机器学习的任务,数据都是构建系统的基石。然后我们创建模型。这两个步骤是任何机器学习研究者都熟悉的。
然而,当你进入像谷歌、Facebook这样的公司,你会发现在编写代码和处理数据之外,还存在其他甚至更重要的步骤。这包括评估、模型调优、模型部署(部署在哪里、如何部署、如何扩展到数百万用户)、获取预测、监控预测性能以及管理模型版本(因为你需要更新、修订甚至废弃模型)。在生产中构建系统涉及如此多的额外步骤,这正是研究与生产之间的差距。
核心组件与挑战
因此,一个机器学习系统需要三个基本要素:数据、模型和代码。重要的是,当进入生产环境时,所有这些方面都可能发生变化。每个方面都需要考虑不同的问题,例如:
- 数据:数据量、采样方法、数据模式很重要。
- 模型:所选算法、模型训练和实验很重要。
- 代码:配置、错误修复和需求管理变得非常重要。
设计流程中的挑战
设计过程本身也充满挑战。通常,不同角色的人员负责不同部分:
- 数据工程师:负责准备和标注数据。
- 数据科学家:负责研究数据、选择合适的模型、确定所需数据量、规划模型更新频率。他们的核心关切通常是获得最准确的模型。
- 机器学习工程师/开发者:负责编写代码(如HTML界面或其他应用),将模型集成到具体应用中,供最终用户使用。他们的核心关切可能是获得最快的预测速度。
这些团队有着完全不同的关切点。例如,数据科学家可能追求高精度而产出庞大的模型(如2GB),而机器学习工程师若想将模型部署到小型设备上,这就会产生矛盾。如果没有适当的过程来协调这些不同的关切,将会面临巨大的问题。
机器学习流水线与工具
我们可以将整个过程视为一个机器学习流水线。这个流水线以数据和代码作为输入,经过处理(如数据分割、特征工程),最终输出一个训练好的机器学习模型。
为了系统化地管理这个涉及多方面的流水线,我们需要专门的工具。对于软件系统,我们有Git来进行代码版本控制。但在机器学习系统中,除了代码,我们还有数据、指标和模型本身,所有这些都需要进行版本控制和管理。
以下是解决这些问题的工具示例:
- DVC (Data Version Control):这是一个类似于Git的工具,但专门用于跟踪机器学习模型和数据集。它可以自动化流水线中的部分或全部过程,允许你提交数据、模型、代码和指标的特定版本,并使用类似
dvc repro、dvc push的命令进行操作。它主要通过利用外部云存储(如S3)来处理大型内容。 - Pachyderm:另一个工具,使用容器技术来执行流水线的不同步骤,处理数据版本控制和数据沿袭等问题。
- MLflow:一个用于管理机器学习生命周期的平台,可以帮助定义文件格式,并在本地或远程进程上执行任务。
这些工具都提供了特定的API或命令行界面,其核心思想类似于Git,但具备管理代码之外内容(如数据和模型)的额外能力,这正是机器学习系统所需要的。
总结

本节课中,我们一起学习了为机器学习设计计算机系统的核心概念。我们认识到,一个可投入生产的机器学习系统是一个复杂的工程,涉及数据、模型、代码三大支柱,并需要协调数据工程师、数据科学家和机器学习工程师等多个角色的工作。我们探讨了系统中隐藏的技术深度、生产流程中的额外步骤(如部署、监控、版本管理),以及支持这些流程的必要工具(如DVC)。理解这些设计原则和挑战,是构建健壮、可扩展的机器学习系统的第一步。


下一讲,我们将继续深入探讨模型服务等更多细节。
2.2:为机器学习设计计算机系统



概述
在本节课中,我们将要学习机器学习系统的另一个重要方面:模型服务。我们将探讨如何将训练好的模型部署到生产环境中,使其能够被最终用户使用。我们还将讨论机器学习系统的测试、实验跟踪以及模型部署的不同策略。
模型服务简介
上一节我们介绍了机器学习系统的设计过程。本节中,我们来看看如何将训练好的模型提供给用户使用,这个过程称为模型服务。
当我们谈论机器学习模型时,我们指的是一个具有特定架构和参数的实体。例如,Inception V3 是一个具有特定架构的深度神经网络。你使用数据训练它,最终会得到一个模型文件,其中包含了所有学习到的参数。
这个模型文件需要被部署到某个地方,我们称之为模型服务器。最终用户通过特定的协议(如 REST RPC)与这个服务器交互,发送请求并获得预测结果。
这个过程类似于传统的软件部署:你编写代码,将其部署在服务器上(如 HTTP 服务器),用户通过网络与服务器交互。对于机器学习系统,你需要一个专门的模型服务器来处理模型的预测请求。
模型服务的不同场景
模型服务的具体形式取决于你的应用场景。以下是几种常见的部署场景:
- Web 应用程序:你可以使用 TensorFlow 训练模型,然后使用 Flask 等框架将模型包装成 Web API。前端应用(如使用 JavaScript)可以通过这些 API 与模型交互。
- 物联网设备:你可能拥有摄像头等 IoT 设备。根据设备能力和延迟要求,你可以选择将模型部署在设备本地进行快速预测,或者将数据发送到云端服务器进行处理。
- 流处理系统:系统需要持续处理来自传感器、视频流或智能手表的数据流。你可以使用 Kafka 等技术处理数据流,并将模型部署在同一位置或单独的服务器上进行实时预测。
随着用户请求量的增加,确保低延迟和高可用性变得至关重要。这可能涉及使用负载均衡器和多个服务器。
模型服务的实现方式
将模型投入生产有几种主要方式:
- 嵌入模型:将模型文件视为应用程序的依赖项,与应用程序代码一起打包。这种方式简单,将模型视为普通代码处理。
- 部署为独立服务:这是更流行的方式。模型被一个包装器包裹,该包装器暴露特定的 API 端点。客户端知道这些端点的地址和协议(如 REST),并远程调用它们来获取预测。
- 将模型视为数据:在这种方式下,部署环境可以在运行时动态加载和使用最新版本的模型。这便于模型的持续更新和迭代。
为了实现不同训练框架(如 PyTorch, TensorFlow)和不同部署环境(服务器、移动端、云)之间的兼容性,可以使用中间交换格式,例如 ONNX。ONNX 提供了一个通用的模型表示格式和运行时引擎,使得你可以用任何框架训练模型,然后轻松地将其部署到多种目标环境中。
机器学习系统的测试
由于训练环境和生产环境之间存在差异,对机器学习系统进行测试至关重要。一个机器学习系统包含三个核心部分:模型、数据和代码,每一部分都需要测试。
以下是测试的几个关键方面:
- 数据验证:确保输入数据的结构和值符合预期。例如,检查特征值是否在有效范围内(如 0 到 1 之间),或检查独热编码向量是否格式正确。
- 特征计算单元测试:编写自动化测试来验证从原始数据计算出的特征是否正确。
- 集成与合约测试:当系统由多个服务组成时,需要测试服务间的接口是否兼容,以及导出的模型能否在生产环境中成功加载和运行。
- 模型质量监控:设置自动化测试来监控生产环境中模型的性能指标(如准确率、精确率、召回率)。当指标低于预定阈值时触发警报。
- 公平性与偏见测试:使用可视化工具(如 Facets)分析数据在不同维度(如种族、性别、地区)上的分布,检查是否存在数据不平衡,这可能导致模型存在偏见。
在研究和生产中进行系统化测试,可以极大地帮助定位和修复问题。
实验跟踪
实验跟踪工具对于观察系统进展和管理模型生命周期非常重要。
这类工具(如 MLflow)可以捕获和展示你关心的各项指标。它们允许你并行运行多个实验,比较不同模型版本或分支的性能。生产团队可以基于这些可视化的指标,决定是否将某个模型版本推广到生产环境,或者在模型性能下降时将其下线。
模型部署策略
在生产中部署模型时,有多种策略可以考虑:
- 多模型并行:同时部署多个模型,以确保系统在某个模型出现问题时仍能持续工作。
- 影子模型:让新模型作为“影子”运行,接收真实流量但不影响实际决策。通过观察其在一段时间内的性能,再决定是否将其提升为生产模型。
- 竞争模型与 A/B 测试:部署多个具有不同架构的模型(如随机森林 vs. 深度神经网络),让它们处理部分流量。通过 A/B 测试比较它们的性能,并根据结果动态调整流量分配或淘汰表现不佳的模型。
- 自动化模型管理:可以构建一个模型监督器,根据实时性能指标自动执行策略,例如将请求从性能下降的模型路由开。这本身也可以被构建为一个学习任务。
这些策略对于像 Facebook、Google 这样大规模使用机器学习模型的公司尤为重要。
总结
本节课中我们一起学习了机器学习系统设计的核心环节——模型服务。我们了解了什么是模型服务及其重要性,探讨了 Web 应用、IoT 和流处理等不同场景下的服务方式。我们还介绍了嵌入、独立服务以及 ONNX 交换格式等具体实现手段。

此外,我们深入讨论了机器学习系统测试的各个方面,包括数据验证、单元测试和公平性检查,并强调了自动化测试的重要性。最后,我们了解了实验跟踪工具的作用以及多模型、影子部署和 A/B 测试等高级部署策略,这些对于构建健壮、可维护的生产级机器学习系统至关重要。
2.3:为机器学习设计计算机系统


在本节课中,我们将要学习机器学习系统构建流程中的另一个重要方面——编排。我们将探讨如何将机器学习管道的各个组件系统地组织起来,形成一个自动化、可扩展的端到端流程。
上一节我们介绍了模型部署、模型服务以及机器学习系统测试。本节中,我们来看看如何将这些独立的组件编排成一个协同工作的完整管道。
🧩 什么是编排?
编排是指将机器学习管道中的不同组件(如数据、模型、应用代码)整合在一起,并确保它们能够作为一个整体顺畅运行的过程。这需要一个系统化的流程,而非手动操作。
一个重要的方面是基础设施。我们需要计算和数据资源来部署系统。这包括服务器、云资源或服务,用于执行模型和应用程序。基础设施的配置对于训练、部署、服务和监控都至关重要。
以下是编排需要涵盖的关键组件:
- 数据管道:构建、测试和部署为模型提供数据的数据管道。
- 测试与验证:在系统运行过程中,对模型进行测试和验证。这在有多个模型版本或需要在线评估新模型(如“影子模型”)时尤为重要。
- 模型部署:将模型部署到模型服务器,以供应用程序调用。
为了实现这种编排,我们需要使用专门的工具,即持续集成和持续交付工具,通常简称为 CI/CD。这些工具为任何软件系统(包括机器学习系统)提供了自动化构建、测试和发布的流程。
🔄 CI/CD 管道概述
在抽象层面,CI/CD 管道包括构建过程、测试过程和发布过程。我们从源代码开始,经过构建、打包、集成测试、预发布,最终部署到生产环境。
对于机器学习系统,这个管道可以进一步细分为两个主要部分:
- ML 管道:负责模型的训练、评估和打包。例如,使用
dvc push命令将训练好的模型打包并发布为一个独立的制品。 - 部署管道:负责构建、测试和部署应用程序代码。应用程序是用户直接交互的端点,它会消费ML管道产出的模型。
需要理解的是,模型和应用程序代码是独立的实体,它们作为不同的制品被分别处理和部署。
🛠️ 管道工具示例
有多种工具可以实现CI/CD管道。以下是一个使用特定工具(如 GoCD)定义管道的示例:
以下是管道仪表板的示例,展示了如何定义和组合不同的任务:
- ML 管道:定义了从代码库获取模型代码、训练、评估到发布模型的完整流程。
- 应用管道:定义了构建、测试和部署应用程序的流程。
- Git 集成:指定了代码所在的分支和版本。
- 基础设施配置:定义了运行管道所需的实例数量等资源信息。
- 监控信息:仪表板会显示任务执行时长等数据。
真实的工业级管道(例如在谷歌内部使用的)要复杂得多,包含大量组件和详细的配置信息,如资源位置、性能指标阈值等。管道的组合方式——即信息如何在不同组件间流动——也非常重要。
❓ 机器学习CI/CD的特殊挑战
一个关键问题是:机器学习项目的持续集成过程与传统软件工程项目相比,最大的挑战和复杂性是什么?
最主要的区别在于监控部分。对于机器学习系统,我们需要监控的指标更加复杂和模糊:
- 模型偏差:如何定义和测量模型的偏差?存在多种可能相互矛盾的定义。
- 性能指标:除了延迟,能耗等指标是否重要?
- 自动化决策:即使确定了指标,如何设定阈值来自动决定是否回滚或更新模型?这通常涉及大量“灰色地带”。
相比之下,传统软件系统的性能指标(如响应时间、吞吐量)定义清晰,自动化监控和决策也相对成熟。机器学习系统的监控更具挑战性,但社区正在这方面取得进展。
📊 模型监控详解
模型监控是编排和运维中的核心环节。一旦系统上线,我们需要决定何时回滚或更新模型。
模型监控主要关注以下几个方面:
-
模型输入与输出:
- 输入:分析生产环境接收的数据分布是否与训练数据分布存在差异。这可以使用分布散度度量(如KL散度)进行数学上的比较。
- 输出:监控模型在生产数据上的预测或推荐结果,评估其真实性能。
-
模型可解释性:
许多机器学习模型(如神经网络)是“黑盒”。我们需要工具来解释模型的预测,理解它为何做出某个决策,从而诊断是否存在过拟合或偏差。- 工具示例:
ELI5、LIME。 - 作用:这些工具可以高亮显示对分类决策重要的图像区域、文本中的关键词或特征的重要性权重,帮助开发者调试模型。
- 工具示例:
🔧 诊断后的干预措施
当通过监控和解释工具诊断出模型存在问题时,我们可以采取多种干预措施:
以下是几种常见的模型干预方法:
- 收集更多数据:如果模型因数据不足而产生偏差,这是最常用的方法。
- 调整正则化:使用更好的正则化方法,提高模型的鲁棒性。
- 超参数调优:调整模型超参数以获得更好性能。
- 修改模型架构:对于深度学习模型,可以调整网络的深度、宽度等。
- 调整集成模型参数:对于随机森林等模型,可以调整树的数量或深度。
通常,从业者会首先从数据入手,但如果数据收集受限,则需要尝试其他方法。
🔁 数据分布变化与模型更新
一个典型场景是:模型在测试集上表现良好(如95%准确率),但在生产环境中一段时间后,准确率大幅下降(如60%)。这通常是因为生产环境的数据分布与训练/测试数据分布发生了偏移。
解决方案是建立一个自动化模型更新管道。该管道可以:
- 监控模型在新数据上的性能。
- 当性能低于某个阈值时,自动触发重新训练流程。
- 使用新收集的生产数据(可能需要人工标注)来更新模型。
- 将更新后的模型集成到部署管道中。
这个更新频率(每天、每周、每月)取决于应用的类型和动态程度。模型更新本身也应作为CI/CD管道的一个组件,与监控和ML管道协同工作。
🧪 模型验证与保证
对于某些关键应用(如自动驾驶、航天器),我们不仅需要监控,还需要形式化验证来提供保证。
- 形式化验证:使用数学方法证明模型在某些条件下绝不会出现意外行为,或给出错误的上界/下界。
- 挑战:当前的形式化验证方法难以扩展到大型模型(如深度神经网络),但对于小型模型是可行的。
- 应用选择:对于不那么关键的应用,可以依赖监控、诊断和更新流程来确保系统可靠性。
📈 用户反馈与奖励机制
监控还应包括用户行为。例如,在推荐系统中,可以跟踪用户是否点击或购买了推荐内容。这种“奖励”信号是理解模型实用性和决定是否需要调整模型的重要反馈。
🛠️ 监控工具栈(如ELK)
实施监控需要工具支持。一个常见的工具栈是 ELK:
- E (Elasticsearch):用于存储和检索日志。
- L (Logstash):用于收集、处理和转发日志。需要在应用程序代码中插入日志记录点。
- K (Kibana):用于可视化和探索日志数据的用户界面。
以下是在机器学习代码中集成日志记录的示例:
# 示例:在模型预测代码中添加日志记录
import logging
from datetime import datetime
logger = logging.getLogger('ml_model')
# 配置Logstash处理器...
def predict(input_data):
time1 = datetime.now()
prediction = model.predict(input_data) # 模型预测
time2 = datetime.now()
inference_time = (time2 - time1).total_seconds()
# 记录预测结果和推理时间
log_data = {
'prediction': prediction.tolist(),
'inference_time': inference_time,
'tenant': 'tenant_1' # 在多租户环境中标识实例
}
logger.info(log_data)
return prediction
通过Kibana,我们可以将推理时间等指标可视化(如绘制分布图),从而诊断性能问题,决定是否需要扩展服务器实例或优化模型。
🏗️ 端到端构建流程总结
将以上所有环节结合起来,就构成了机器学习系统的端到端构建流程:
以下是完整的、循环往复的构建与部署流程:
- 模型构建:使用代码在训练数据上训练模型,产出候选模型。
- 模型评估与实验:使用测试数据、生产数据和定义好的指标评估候选模型,选择最佳模型。
- 应用测试:测试集成了模型的应用程序代码。
- 打包与部署:将应用代码和选定的模型打包成制品,部署到生产环境。
- 生产监控:使用ELK等工具监控生产环境的性能、数据分布、用户反馈等。
- 循环与更新:根据监控结果,触发数据收集、模型重新训练、评估和重新部署的流程。
这个过程是持续不断的循环,构成了持续集成、持续交付和持续部署的基础。目标是确保机器学习应用始终可用、安全、可靠,并让用户满意。
🤔 问答与补充
- 何时扩展模型实例? 这取决于你关心的指标。例如,如果监控到推理延迟出现异常,为了保障用户体验,就需要增加后端模型实例的数量。这与为了公平性等原因更新模型是不同的维度。
- 容器要求? 没有特定的编程容器要求,可以根据需要选择任何容器技术。
- 多模型项目如何使用管道? 可以扩展CI/CD管道来支持多个模型。可以为每种模型类型定义类似的子管道(如神经网络管道、SVM管道),然后在应用管道中适当地组合这些模型,并为每个模型设置独立的监控和更新流程。

本节课中我们一起学习了机器学习系统的编排与端到端构建流程。我们探讨了CI/CD管道的概念、模型监控的挑战与方法、诊断后的干预措施,以及如何利用工具栈实现自动化运维。理解这些工程实践对于构建可靠、可扩展的工业级机器学习系统至关重要。
3.2:感知机与逻辑回归 🧠

在本节课中,我们将要学习两种基础的线性分类器:感知机和逻辑回归。我们将从简单的二元分类开始,逐步扩展到多类别分类,并探讨如何将非概率模型转化为概率模型,以获得更好的理论性质和性能。
感知机:二元分类
上一节我们介绍了分类器的基本概念,本节中我们来看看一个非常简单的线性分类器——感知机。感知机的基本思想是找到一个权重向量 W,通过计算输入特征向量 F(x) 与 W 的内积来进行分类决策。
其决策规则如下:
- 如果 W · F(x) > 0,则预测为正类(+1)。
- 如果 W · F(x) < 0,则预测为负类(-1)。
感知机的学习过程是一个迭代算法。以下是其核心步骤:
- 初始化权重向量 W 为零。
- 遍历每个训练样本 (x, y*),其中 y* 是真实标签(+1 或 -1)。
- 使用当前 W 对样本 x 进行预测,得到预测标签 y。
- 如果预测正确(y = y*),则保持 W 不变。
- 如果预测错误,则按以下规则更新权重:
- W = W + y · F(x)*
这个更新规则的含义是:如果权重向量对当前样本做出了错误判断,我们就将其向该样本的特征向量方向(若真实标签为正)或相反方向(若真实标签为负)调整,以期下次能做出正确判断。
- W = W + y · F(x)*
这个过程不断重复,直到所有训练样本都被正确分类,或达到预设的迭代次数。
感知机:多类别分类
理解了二元感知机后,我们来看看如何将其扩展到处理两个以上的类别。多类别感知机的基本思想是为每个类别 c 都维护一个独立的权重向量 W_c。
对于输入 x,我们为每个类别计算一个“激活值”:
z_c = W_c · F(x)
分类决策不再是看正负,而是选择激活值最高的类别作为预测结果:
预测标签 y = argmax_c (z_c)
学习过程与二元情况类似,但更新规则稍有不同。当对一个样本 (x, y*) 预测错误时(预测标签 y ≠ y*):
- 对于错误预测的类别 y 的权重向量:W_y = W_y - F(x) (使其远离当前样本)。
- 对于真实类别 y* 的权重向量:W_y* = W_y* + F(x) (使其靠近当前样本)。
- 对于其他所有类别,权重保持不变。
以下是一个文本分类的简单示例,类别为“体育”、“政治”、“科技”:
- 初始权重全为零。
- 句子“选举投票”的真实标签是“政治”,但当前模型可能错误地将其分类为“体育”。
- 更新:从“体育”的权重向量中减去该句子的特征向量,并向“政治”的权重向量中加上该特征向量。
- 然后继续处理下一个样本,并重复此过程,直到模型在训练集上不再犯错或达到收敛。
感知机的局限性与改进方向
虽然感知机简单直观,但它存在一些明显的局限性:
- 对噪声和非线性可分数据不友好:如果数据不是线性可分的,感知机算法可能永远无法收敛,会在某些样本上反复犯错。
- 找到的决策边界可能不理想:即使数据是线性可分的,感知机也可能找到一个“勉强”分开所有数据的边界,而这个边界可能非常靠近某一类数据点,泛化能力较差。它没有机制去选择一个“最大间隔”的、更稳健的边界。
- 容易过拟合:如果持续迭代直到训练误差为零,可能会严重过拟合训练数据,在未见过的测试数据上表现糟糕。
解决这些问题的核心思路之一是引入概率框架。感知机输出的是确定的类别标签(0或1),而概率模型输出的是属于每个类别的概率。这使我们能够量化预测的置信度,并使用更丰富的数学工具(如最大似然估计)来寻找最优的模型参数,而不仅仅是找到一个能分开数据的任意解。
逻辑回归:引入概率模型
为了克服感知机的局限性,我们引入逻辑回归。逻辑回归可以看作是感知机的概率化版本。对于二元分类,我们希望模型输出样本 x 属于正类的概率 P(y=+1 | x, W)。
我们需要一个函数将激活值 z = W · F(x)(可正可负)映射到 [0, 1] 区间,并且是单调的。Sigmoid函数 正是这样一个函数:
P(y=1 | x, W) = σ(z) = 1 / (1 + e^{-z})
当 z 趋近正无穷时,概率趋近于1;当 z 趋近负无穷时,概率趋近于0。这样,我们就得到了一个合法的概率分布。
我们的目标不再是简单地减少分类错误,而是寻找一组权重参数 W,使得模型为所有训练样本的真实标签赋予的联合概率(似然)最大。这被称为最大似然估计:
最大化 ∏_i P(y_i | x_i, W)*
其中 y_i* 是第 i 个样本的真实标签。
通过最大似然准则,我们可以在所有能完美分类数据的权重向量中,选择一个为训练数据赋予最高似然值的向量,这通常对应着一个更合理、泛化能力更强的决策边界。
逻辑回归:多类别分类(Softmax回归)
将概率化的思想扩展到多类别,我们就得到了多类别逻辑回归,或称 Softmax回归。
首先,我们为每个类别 c 计算激活值 z_c = W_c · F(x)。然后,使用 Softmax函数 将这些激活值转换为一个概率分布:
P(y=c | x, W) = e^{z_c} / (∑_j e^{z_j})
Softmax函数确保:
- 每个类别的概率都在0到1之间。
- 所有类别的概率之和为1。
- 它具有“放大”效应:最大的激活值对应的概率会被进一步增强,使得概率分布更倾向于某一个类别,这符合分类任务的需求。
多类别逻辑回归的学习目标同样是最大化训练数据的似然:
最大化 ∏_i P(y_i | x_i, W)*
这里 P(y_i | x_i, W)* 就是通过Softmax函数计算出的、样本 i 属于其真实类别 y_i* 的概率。

本节课中我们一起学习了线性分类的两大基础模型:感知机和逻辑回归。我们了解了感知机如何通过简单的迭代更新进行二元和多类别分类,也认识到了其作为非概率模型的局限性。随后,我们探讨了如何通过引入Sigmoid和Softmax函数,将感知机转化为概率模型——逻辑回归,从而能够利用最大似然估计来寻找更优的模型参数。这为我们接下来学习更复杂的模型和优化算法奠定了重要的基础。
4.1:优化与神经网络 🧠

在本节课中,我们将要学习优化的核心概念,特别是如何利用微积分工具来寻找机器学习模型(如逻辑回归)的最佳参数。我们将从简单的线性分类器回顾开始,逐步深入到梯度下降这一关键优化算法。
回顾:从分类器到优化问题
上一节我们介绍了简单的线性分类器和逻辑回归模型。本节中我们来看看如何为这些模型找到最佳的权重参数。
我们讨论了二元分类器和多元分类器。对于多元分类,我们使用Softmax函数将激活值转换为概率分布。这为我们提供了一个原则性的方法来评估决策边界的好坏。
我们的核心目标是找到一组权重参数 w,使得训练数据的对数似然概率最大化。这定义了一个需要被最大化的函数 G(w)。
优化挑战:连续空间 vs. 离散空间
在人工智能课程中,我们处理过约束满足问题(CSP)等离散优化,可以使用爬山法等技术。然而,机器学习中的权重参数 w 存在于连续空间中,有无穷多种可能的方向和长度。我们无法枚举所有邻近的 w,因此需要一种能提供“移动方向”信号的更优方法。
梯度下降的直观理解 🧗
想象你身处山中,目标是登上山顶。你无法看清周围,但有一个设备(如GPS)可以告诉你当前位置的海拔。为了找到上山的路,你可以向四周试探性地迈出一小步,测量每一步的海拔变化,然后选择海拔增加最多的方向前进。在数学上,这个“海拔变化率”就是函数的导数。
在一维情况下,权重 w 是一个标量。我们从初始值 w₀ 开始,计算函数 G 在 w₀ 处的导数。导数为正,意味着向右移动会增加函数值;导数为负,则向左移动。我们沿着导数指示的方向,以一定的步长(学习率 α)迭代更新 w,直到导数接近零,此时我们到达了一个局部最优点。
更新公式(一维):
w_new = w_old + α * (dG/dw)
扩展到多维:梯度向量 🧭
当权重 w 是多维向量时(例如 w = [w₁, w₂]),情况类似,但我们需要决定在每个维度上移动多少。这时我们使用梯度(Gradient),它是一个向量,每个分量是函数 G 对相应权重分量的偏导数。
梯度定义:
∇G(w) = [ ∂G/∂w₁, ∂G/∂w₂, ..., ∂G/∂w_n ]
更新公式(多维):
w_new = w_old + α * ∇G(w_old)
学习率 α 控制着更新的步幅。如果 α 太大,可能会越过最优点甚至发散;如果 α 太小,收敛速度会非常慢。通常,在远离最优点时可以使用较大的 α,在接近最优点时应减小 α 以精细调整。
为什么梯度方向是最速上升方向?📈
我们可能想知道,沿着梯度方向移动是否真的是函数值增加最快的方向。利用一阶泰勒展开,我们可以证明,在给定一个最大步长约束(ε)下,为了使函数值 G(w+Δ) 增加最多,移动向量 Δ 应该与梯度向量 ∇G(w) 的方向完全一致。
数学原理:
我们希望最大化 G(w+Δ) ≈ G(w) + ∇G(w)·Δ,其中 ||Δ|| ≤ ε。
两个向量的点积在其方向相同时最大。因此,最优的 Δ 是 Δ = ε * (∇G(w) / ||∇G(w)||),即沿着梯度方向移动。
这严格证明了梯度方向确实是当前位置函数值上升最快的方向。
算法化:梯度上升/下降法 ⚙️
将上述数学原理转化为算法,就得到了梯度上升法(最大化函数)或梯度下降法(最小化函数)。对于我们的对数似然最大化问题,我们使用梯度上升法。
以下是算法的通用步骤:
- 初始化权重向量 w。
- 循环直到收敛:
a. 计算当前 w 处目标函数 G(w) 的梯度∇G(w)。
b. 按照公式更新权重:w = w + α * ∇G(w)。
c. (可选)根据策略调整学习率 α。 - 返回最终的权重 w。
对于机器学习,我们的目标函数 G(w) 就是训练数据的对数似然函数。将这个特定的函数代入上述通用框架,我们就能找到逻辑回归等模型的最佳参数。
总结与展望
本节课中我们一起学习了优化的核心思想。我们从离散优化与连续优化的区别讲起,直观地理解了梯度作为“方向指南”的作用,并严格证明了梯度方向是最速上升方向,最后将其形式化为梯度上升/下降算法。
这个算法是通用的,但应用于具体的机器学习模型(如逻辑回归)时,我们还需要考虑一些具体细节,例如如何高效地计算梯度(特别是对于大规模数据),以及如何选择和处理训练样本。这些将是后续课程的重点。

希望你能享受这个周末,我们下次课将继续深入这些细节。请注意安全。
4.2:优化与神经网络 🧠

在本节课中,我们将要学习梯度下降/上升优化算法,并了解如何将其从简单的逻辑回归模型扩展到深度神经网络。我们将探讨算法的原理、实现细节以及神经网络的核心概念。


梯度上升算法回顾
上一节我们介绍了梯度上升算法。本节中,我们来看看其背后的数学原理。
梯度上升是一种用于寻找函数最优参数的迭代方法。在分类问题中,我们的目标是找到一组权重参数,使得模型能基于训练数据给出良好的分类结果。
该算法从一个随机的权重初始化开始。在权重空间(例如,由W1和W2两个维度构成的空间)中,我们从某点出发,沿着梯度的方向移动。这是一个迭代过程:在每一步,我们计算当前点的梯度,然后根据梯度找到权重空间中的下一个点。我们重复此过程,直到收敛到某个点,我们称之为“最优解”。

为什么梯度方向是最优方向?
现在,我们来探讨为什么沿着梯度方向移动是一个合理且最优的选择。
假设我们有一个目标函数 G(W),并且对权重的变化量 Δ 有一个约束(例如,其范数固定)。我们希望找到能使 G 增加最多的 Δ。
根据一阶泰勒展开,函数的变化可以近似为:
G(W + Δ) ≈ G(W) + Δ₁ * ∂G/∂W₁ + Δ₂ * ∂G/∂W₂
我们希望最大化这个变化量。在给定 Δ 的约束下,最大化内积 Δ · ∇G(W) 的方法是让 Δ 与梯度 ∇G(W) 的方向完全一致。因此,梯度方向就是在约束下使函数值增长最快的方向。这从理论上证明了梯度上升(或下降)算法在每一步都选择了正确的移动方向。
对于更高维度(N维)的情况,方法是相同的。梯度是一个N维向量,我们可以遵循相同的迭代过程。
梯度上升算法流程
以下是梯度上升算法的标准步骤:
- 初始化权重:随机初始化权重向量 W。
- 迭代更新:进入循环,直到满足停止条件。
- 计算当前权重 W 下目标函数 G(W) 的梯度 ∇G(W)。
- 按照以下规则更新权重:W_new = W_old + α * ∇G(W_old)
- 其中 α 是学习率,通常是一个很小的正数。
学习率 α 的选择很重要。它可以是一个固定值,也可以在训练过程中动态调整。通常,随着我们接近最优解,学习率会逐渐减小,以防止在最优值附近震荡。一个经验法则是,学习率的大小大约为目标函数值的1%。
在分类问题中,我们的目标函数 G(W) 是给定输入和权重时,标签的对数似然。我们希望最大化整个训练集 D 上的对数似然总和:
∑ log P(Y_i | X_i, W)
使用对数似然主要是为了数学上的便利,它能使梯度计算和优化过程更简单。
随机梯度上升
在实际应用中,训练集可能非常庞大。逐样本计算整个数据集的梯度计算量很大。
随机梯度上升是标准梯度上升的一个变体。其核心思想是:在每次迭代中,我们不使用全部训练样本来计算精确梯度,而是随机选取一小批(Mini-batch)样本,用它们来估计梯度。这个估计是有噪声的,但它仍然提供了有用的更新方向。
更新过程如下:
- 从初始权重 W0 开始。
- 随机选择一批样本,用这批样本计算对数似然的梯度(这是一个对真实梯度的噪声估计)。
- 根据这个噪声梯度估计更新权重,得到 W1。
- 重复步骤2和3,每次选择不同的随机批次。
批次大小的选择是一个权衡:
- 大批次:梯度估计更准确,但每次迭代计算更慢,需要更多计算资源。
- 小批次:梯度估计噪声大,但每次迭代计算更快。
批次大小可以根据可用的计算资源(如GPU核心数)进行调整,范围可以从1(随机梯度下降)到整个训练集大小(批量梯度下降)。
从逻辑回归到神经网络
我们已经学会了如何优化多类逻辑回归模型。现在,我们将其扩展到神经网络。
逻辑回归的核心在于:我们有一组给定的特征向量,通过权重计算激活值 Z,再通过Softmax函数将其转换为类别概率分布。学习过程就是寻找能最大化训练数据似然的权重。
Z = W · X (点积)
P(Y=k | X, W) = softmax(Z)_k
在传统机器学习中,特征 X 需要人工设计或通过编写程序从原始数据(如图像像素)中提取。这需要领域知识。
神经网络的突破性思想是:我们能否也自动学习这些特征表示? 答案是肯定的。
神经网络可以看作是逻辑回归的堆叠和扩展。我们不再使用手工设计的特征,而是构建多个“层”,每一层都执行类似逻辑回归的变换,但其输入是前一层的输出。
神经网络的前向传播
在一个神经网络层中,计算第 k 层第 i 个神经元的激活值 Z_i^(k) 的公式如下:
Z_i^(k) = g( ∑_j W_ij^(k) * a_j^(k-1) )
其中:
- a_j^(k-1) 是前一层的第 j 个神经元的激活值(对于第一层,就是输入 X)。
- W_ij^(k) 是连接前一层的神经元 j 到当前层神经元 i 的权重。
- g(·) 是一个非线性激活函数。
- 求和后,结果通过激活函数 g 产生当前神经元的激活值。
每一层都有不同数量的神经元(节点)。权重的大小决定了连接的重要性。
非线性激活函数的重要性
非线性激活函数 g(·) 至关重要。如果没有它,整个网络的变换将保持线性组合。无论堆叠多少层,最终效果都等价于一个单层线性模型。如果数据不是线性可分的,这样的模型将无法正确分类。
常用的非线性激活函数包括:
- Sigmoid: g(z) = 1 / (1 + e^{-z}),将输出压缩到(0,1)之间。
- Hyperbolic Tangent (tanh): g(z) = tanh(z),将输出压缩到(-1,1)之间,以0为中心。
- ReLU (Rectified Linear Unit): g(z) = max(0, z),这是最常用的激活函数之一。
ReLU之所以流行,是因为其计算非常简单(无需指数运算),且能缓解深度网络中的“梯度消失”问题。其梯度计算也很简单:输入为正时梯度为1,为负时梯度为0。激活函数的选择没有绝对规则,通常基于实验(试错)和具体任务来决定。
神经网络的万能近似定理
一个重要的理论保证是万能近似定理。该定理指出,一个至少包含一个隐藏层的前馈神经网络,只要隐藏层拥有足够多的神经元,就可以以任意精度近似任何连续函数。
需要注意的是,这个定理只证明了“存在性”,并没有告诉我们具体需要多少个神经元,也没有提供找到这个网络的方法。因此,它在实践中更多是一个理论安慰。
实践与模型选择
理解理论后,实践中的模型设计(如层数、每层神经元数、激活函数选择)至关重要。这通常是一个探索过程:
- 从简单开始:尝试一个浅层网络。
- 增加复杂度:如果欠拟合,增加层数或神经元数。
- 调整激活函数:ReLU通常是默认选择,但可以尝试Sigmoid或tanh。
- 正则化:使用如Dropout、L2正则化来防止过拟合。
- 批次大小与学习率:调整这些超参数以稳定和加速训练。
这个过程本身可以被自动化,即“神经架构搜索”,这是一个活跃的研究领域。
总结
本节课中我们一起学习了:
- 梯度上升/下降算法的原理及其数学证明,理解了为什么梯度方向是最速上升方向。
- 随机梯度上升的动机与实现,它通过使用小批次样本来平衡计算效率和梯度估计精度。
- 如何将多类逻辑回归自然地扩展到神经网络。关键区别在于神经网络能够自动学习特征表示,而无需人工设计。
- 神经网络的前向传播计算方式,以及非线性激活函数的核心作用。
- 神经网络强大的理论保证——万能近似定理,以及实践中通过试错和调整超参数来设计有效模型的流程。

通过结合优化算法和灵活的模型结构,神经网络为我们提供了构建强大机器学习系统的基石。
5.1:朴素贝叶斯学习 🧠



在本节课中,我们将要学习一种新的、不同类型的机器学习模型——朴素贝叶斯。它与我们之前讨论的感知机、神经网络等模型有本质区别,因为它是一种基于概率的模型,为我们提供了理解模型内部工作原理的视角。
模型概览:从判别式到生成式
上一节我们介绍了基于激活函数的判别式模型。本节中我们来看看生成式模型。
到目前为止,我们讨论的模型(如感知机、神经网络)都属于判别式模型。它们的工作原理是:输入特征向量 F(x),经过一系列激活函数 Z 的计算,最终输出一个用于决策的标签。我们可以将这些激活值转换为概率,以便更好地进行决策。
然而,朴素贝叶斯是一种生成式模型。它的目标不是直接学习从特征到标签的映射,而是学习数据的联合概率分布 P(Y, F1, F2, ..., Fn)。这个联合分布描述了数据(特征和标签)是如何“生成”的。有了这个生成模型,我们就可以通过贝叶斯定理来计算给定特征下标签的后验概率 P(Y | F1, F2, ..., Fn),从而进行分类。
生成式模型的核心优势在于其可解释性。因为模型内部是概率结构,我们可以基于概率论原理来推理模型结构、学习参数甚至发现隐藏概念,而不是像神经网络那样被视为“黑箱”。
贝叶斯网络基础
要理解朴素贝叶斯,需要一点贝叶斯网络的基础。贝叶斯网络是一种用有向无环图表示变量间依赖关系的概率模型。
在贝叶斯网络中:
- 节点代表随机变量(可以是概率或条件概率)。
- 边代表变量间的依赖关系(如相关性或因果关系)。
- 如果两个变量间没有边连接,则意味着它们在给定其他变量时可能是条件独立的。
这种图结构的关键优势在于它简化了联合概率的计算。如果没有独立性假设,计算所有变量组合的联合概率表会非常庞大(面临“维数灾难”)。而利用图中的条件独立性,我们可以将复杂的联合概率分解为一系列更小的、易于管理的条件概率的乘积,从而大幅减少计算和存储开销。
朴素贝叶斯模型结构





理解了贝叶斯网络的优势后,我们来看朴素贝叶斯采用的特定结构。


朴素贝叶斯模型的结构非常简单,它做了一个很强的“朴素”假设:在给定类别标签 Y 的条件下,所有特征变量 F1, F2, ..., Fn 都是相互独立的。
这个假设对应以下贝叶斯网络结构:
- 有一个父节点,代表类别标签 Y。
- 有 n 个子节点,分别代表特征 F1, F2, ..., Fn。
- 所有特征节点都只依赖于类别节点 Y,并且彼此之间没有连接。
以下是两种结构的对比,说明了为何朴素贝叶斯的结构更优:
-
低效的结构(特征决定标签):
如果箭头从各个特征指向标签(F1, F2, ..., Fn -> Y),那么计算联合概率P(Y, F1, ..., Fn)需要估计P(Y | F1, ..., Fn)。这是一个巨大的条件概率表,大小随特征数量指数增长,无法解决维数灾难。 -
高效的结构(标签决定特征):
朴素贝叶斯采用的结构是标签指向各个特征(Y -> F1, Y -> F2, ..., Y -> Fn)。基于此结构,联合概率可以分解为:
P(Y, F1, ..., Fn) = P(Y) * P(F1|Y) * P(F2|Y) * ... * P(Fn|Y)
现在我们只需要存储:- 一个先验概率表
P(Y),大小是类别数。 - n 个条件概率表
P(Fi|Y),每个表的大小是(特征Fi取值数 × 类别数)。
这比第一种结构所需的存储空间小得多,计算也高效。
- 一个先验概率表
因此,朴素贝叶斯模型需要学习两部分内容:
- 结构:即上述的图结构(通常是固定的)。
- 参数:即先验概率
P(Y)和所有条件概率P(Fi|Y)。
模型推理:如何进行分类
学习了模型的结构和参数后,我们来看如何进行预测(推理)。
分类的目标是:对于给定的特征值 x = (f1, f2, ..., fn),计算后验概率 P(Y | x),并选择概率最大的类别作为预测标签。
根据贝叶斯定理和朴素独立性假设,后验概率的计算如下:
-
计算未归一化的联合概率(生成模型):
对于每一个可能的类别y_k,计算:
P(y_k, f1, ..., fn) = P(y_k) * P(f1|y_k) * P(f2|y_k) * ... * P(fn|y_k)
这个值正比于我们想要的后验概率。 -
归一化得到后验概率(判别模型):
由于上一步得到的值可能非常小(多个概率相乘),且所有类别的这些值之和不一定为1,我们需要进行归一化:
P(y_k | f1, ..., fn) = P(y_k, f1, ..., fn) / Σ_j [P(y_j, f1, ..., fn)]
其中分母是对所有可能类别y_j的求和。这样就得到了一个标准的概率分布。 -
做出决策:
选择后验概率最大的类别作为预测结果:
预测标签 = argmax_{y_k} P(y_k | f1, ..., fn)
在实际计算中,由于概率连乘可能导致数值下溢(得到极小的浮点数),我们通常对概率取对数,将连乘变为连加,这在数学上是等价的,但计算上更稳定:
log P(y_k, f1, ..., fn) = log P(y_k) + log P(f1|y_k) + ... + log P(fn|y_k)
然后比较各个类别的对数概率大小即可。
应用示例
以下是朴素贝叶斯在两个经典问题中的应用。
示例一:数字识别
假设我们要识别手写数字(0-9)。
- 特征:可以是图像的原始像素值(例如,28x28的图像有784个特征,每个特征表示一个像素是黑还是白,或者是灰度/RGB值)。
- 标签:数字0到9。
- 模型参数:
- 先验 P(Y):可以假设均匀分布(每个数字概率为0.1),或者从训练数据中统计每个数字出现的频率。
- 条件概率 P(Fi|Y):对于每个像素位置 i 和每个数字 y,从训练数据中统计:当真实标签是 y 时,像素 i 被激活(是黑色/白色)的概率是多少。
- 预测:当一张新图片输入时,模型根据其像素激活模式,利用上述公式计算它属于每个数字的概率,并选择概率最高的那个。
示例二:垃圾邮件分类
假设我们要区分垃圾邮件(Spam)和正常邮件(Ham)。
- 特征:可以是将邮件文本分词后,每个词是否在邮件中出现(词袋模型)。
- 标签:Spam 或 Ham。
- 模型参数:
- 先验 P(Y):例如,根据历史数据,可能60%是正常邮件,40%是垃圾邮件。
- 条件概率 P(单词|Y):例如,统计在垃圾邮件中,“免费”、“获奖”、“立即点击”等词出现的概率;在正常邮件中,“会议”、“项目”、“问候”等词出现的概率。
- 预测过程:对于一封新邮件,模型依次查看其中的每个词。每看到一个词,就根据该词在垃圾邮件和正常邮件中的条件概率,更新对邮件类别的判断(通过连乘或对数连加)。看完所有词后,比较最终两个类别的(对数)概率,概率高的即为预测类别。这个过程直观地展示了证据(词语)如何一步步影响模型的判断。
过拟合与平滑技术
任何机器学习模型都需要关注泛化能力,朴素贝叶斯也不例外。
朴素贝叶斯可能遇到的一种典型过拟合问题是零概率问题。如果某个特征值在训练数据中从未与某个类别同时出现过,那么根据最大似然估计,条件概率 P(特征|类别) = 0。这将导致在预测时,只要这个特征出现,整个类别的联合概率就会变成0(因为连乘中有一项为0),从而完全否决这个类别,即使其他特征都强烈支持它。
为了解决这个问题,我们需要使用平滑技术(或正则化)。最常见的方法是拉普拉斯平滑(加一平滑)。
其核心思想是:在计算条件概率时,为每个特征的每个可能取值的计数都加上一个小的常数(通常是1),然后再进行归一化。公式如下:
P(Fi = v | Y = y) = (count(Fi=v, Y=y) + α) / (count(Y=y) + α * |Fi|)
其中:
count(Fi=v, Y=y)是训练集中特征Fi取值为v且类别为y的样本数。count(Y=y)是训练集中类别为y的样本总数。|Fi|是特征Fi所有可能取值的个数。α是平滑参数(α=1时即为拉普拉斯平滑)。
平滑确保了即使某个特征-类别组合在训练集中未出现,其条件概率也不会是零,而是一个很小的正数,从而避免了零概率导致的预测失败,提高了模型的泛化能力。

本节课中我们一起学习了朴素贝叶斯分类器。它是一种基于概率的生成式模型,通过“给定类别下特征条件独立”的朴素假设,简化了模型结构,使联合概率的计算变得高效。我们了解了其模型结构、参数学习、基于贝叶斯定理的推理过程,并通过数字识别和垃圾邮件分类的例子加深了理解。最后,我们探讨了零概率问题及其解决方案——平滑技术,这是保证模型泛化能力的关键。朴素贝叶斯虽然“朴素”,但其原理清晰、实现简单,在许多任务中仍然是有效的基线模型。
5.2:Athena 集成防御框架概述与项目介绍

在本节课中,我们将学习一个名为 Athena 的对抗性机器学习防御框架。该框架的核心思想是,将许多多样化的弱防御集成起来,可以形成一个强大的防御系统。我们将了解其工作原理、评估结果,并详细介绍本课程的项目安排。
项目周介绍与安排
大家好。本周我们将重点转向项目介绍。本周是项目周。
计划是首先为大家提供一个高层次的项目描述。虽然大家已经通过提前分享的论文了解了更多细节,但我和Yin撰写了一份项目描述,并已分享在GitHub上。我们计划首先带大家浏览项目概述,请务必提出问题,因为充分理解方法和细节非常重要。
此外,计划在周四由Yin带领大家了解一些代码和框架的实现细节,以便大家更好地理解如何开始和着手这个项目。
在开始之前,大家有任何问题吗?希望我的声音清晰。好的,没有问题。我确实有一个问题,我还没有看到任务的截止日期,不知道是还没有确定吗?这是一个很好的问题。Yin将在今天或周四与大家分享截止日期。是的,我将在今天演示后分享每个任务的截止日期。
还有其他问题吗?好的,我们开始。
Athena:基于多样化弱防御集成的强大防御
今天,我很高兴介绍我们在Athena上的工作,这是一个由许多多样化的弱防御组成的集成防御框架。其中,多样性和多个弱防御是这个防御框架构建的两个最重要方面。
这项工作是多位优秀研究者合作的成果,由我的学生Yin(也是本课程的助教)主导,Jianhai也为此项目提供了大量帮助。我们最初与Jason合作启动了这个项目,最近Forrest等人也加入了这项努力。我也在其中扮演了非常积极的角色。
几周前,我在Google做了这个报告,因此我将使用相同的幻灯片。
对抗性示例与安全背景
这部分内容涉及机器学习的安全性。具体来说,是关于深度学习的对抗性安全,但广义上,对抗性机器学习这一主题不仅限于深度学习系统,而是适用于任何机器学习系统。由于许多深度学习系统已部署在现实场景中,展示它们可能被攻击和防御的事实使其变得更加具体和有趣,因此我们将讨论的场景主要围绕深度学习。
我们讨论过对抗性示例。我不会在此花费太多时间,但为了让大家再次理解,我们讨论过,通常当我们谈论对抗性示例时,我们有一个特定的输入(例如图像,但不限于图像)。然后,我们基于两个方面生成一些扰动:首先是输入本身,其次是输入的正确标签以及从模型中获取的信息(主要是损失函数的梯度)。
直观地说,这种优化沿着某个方向移动,如果有人添加了小的扰动,就可以改变模型做出的决策,从而欺骗它。直观地,我们讨论分类器。你已经对分类器和决策边界有了一些直觉。具体来说,在线性分类器中,我们有一些样本。基于这些样本,你构建了一个决策边界。这里发生的是,你有一个这样的样本,为了欺骗模型,你想添加一些扰动,以改变决策边界,从而将该样本分类为负例。技巧是通过移动样本来越过决策边界以欺骗模型。
人们已经证明,即使通过翻转一定角度,也可以欺骗模型。还有人尝试使用3D打印机来证明这不必是数字图像,也可以是物理物体。例如,你可以打印出这个扰动,使其在右侧被识别。还有一些其他出色的工作表明,你也可以3D打印这些物品,以进行识别或欺骗语音识别系统。对于强化学习和自然语言处理也是如此,这是相同的思想。
这可能非常重要,因为如果我们在实践中部署这样的系统,比如自动驾驶汽车,如果有人能欺骗这些模型,那将非常危险。
对抗性示例的来源与形式化
我们现在讨论这些对抗性示例来源的一些直觉。基本上,这是你已经掌握的关于分类器和机器学习模型的基础知识。
通常,我们从一些数据D开始,其中一些数据可能属于不同的类别。例如,我们有三个类别的样本:橙子、棕榈树和黑猩猩。然后我们有一个模型(例如神经网络),你知道这些神经网络由一些权重值参数化。这些是模型的参数。
训练模型后,你可以选择一个输入样本及其真实标签。我们尝试模型的一个版本(用参数θ1表示),看看得到什么。这个版本的模型将样本分类为“enre”,这显然是错误的。我们可能想重新训练模型或添加更多样本,然后尝试不同的模型,看看预测是否被纠正。在这个例子中,是的,它将样本分类为橙子。这意味着这个模型足够好。
更抽象地说,整体方法是找到一些最优参数θ*,以最小化期望损失L。这个损失是模型相对于来自特定分布D的样本的期望误差。我们需要插入这些最优参数来计算损失。这本质上是一个搜索问题,我们需要找到最优参数,以最小化来自该分布的所有样本的期望损失。
更具体地说,尽管训练的目的是找到最小化损失的参数(我们已经广泛讨论过梯度下降算法可以为我们找到这些参数),但另一方面,你可以将其视为一场博弈,其中对手的目标完全相反。对手试图找到一些小的扰动,在另一方面最大化损失。这是对手的关键目标。你可以将其视为一种博弈情境,双方有不同的目标。
大家对此有任何问题吗?我假设这些内容大家应该已经很熟悉了。这就是为什么我们特意为大家建立这些基础,以便大家此时能够进行项目。
Athena框架的核心思想:输入变换
我将具体讨论Athena,也就是你们要做的这个框架。它基于输入变换的直觉,即我们想以某种方式对输入样本进行一些更改。
为了让大家理解它为何有效,使用输入变换作为防御机制的想法在这项工作之前就已经存在。关键思想是:假设对手找到了一些小的扰动,可以改变这个输入。你可以看到添加了一些噪声。这些扰动可以欺骗这个模型,使其以一定的置信度将样本分类为“9”。之前,模型以相对较高的置信度认为输入是“7”,现在模型认为它是“9”,但置信度一般。
这种变换的作用是改变输入中的噪声。需要讨论的是,这些扰动不是随机的。相反,它们来自一个特定的优化公式,旨在最大化损失。因此,这不是随机的扰动,随机扰动无效。这些是非常精心设计的扰动。在这种旋转情况下,这种变换基本上打乱了这些噪声。因此,我们预计这些噪声(扰动)不会像以前那样有效。这就是为什么在大多数情况下,像这样的变换(具体来说是几何变换,如旋转)可以改变分类器的决策,使其现在以相对中等的置信度认为它是“7”。
这就是变换最初起作用的关键思想。人们之前已经展示过这一点。但这项工作的关键见解在于许多变换或许多弱防御的互补性方面。
在这里,你可以看到不同的对抗性攻击(不同的名称)。在每一行,你可以看到不同的变换,例如压缩、去噪、滤波、几何变换、形态学变换等。让我们看看我们得到了什么。这是对正在发生的事情的一些经验性理解。
如果你注意到某些变换,比如压缩,能够相当正确地恢复标签。你可以看到,这些攻击能够在所有情况下欺骗这个模型。在这个FGSM案例中,模型认为它是9,9,9,8,9,2,9,9,9。在所有情况下,未防御的模型都被欺骗了。但在这里,压缩能够在除“单像素”攻击生成的那些样本之外的所有情况下恢复真实标签。
但其他一些变换,比如几何变换,效果就不那么有效。它能够恢复一些,但对其他一些攻击无效。如果你看这里,我们有一些不同的变换。有趣的部分是,例如,在这种情况下,“去噪”能够相对较好地恢复,特别是能够恢复这个“单像素”攻击生成的样本,而压缩无法恢复。所以你可以看到,一些变换是互补的。
基于此,我们已经看到防御的有效性因攻击而异,并且它们有相互补充的趋势。你可以按列查看。基于这种直觉,大约两年前项目开始时,我们想,也许如果我们把一些这样的变换放在一起,创建一个弱防御的集成(在这种情况下是相同的变换,但我们基于变换训练模型,稍后我会描述如何做),那么这可能是一个强大的防御,因为我们没有假设任何关于攻击的特定信息。
我们没有基于对攻击的预先假设来构建这个防御。当我们希望在现实世界中部署这种防御时,这一点很重要,因为在网络安全中,如果我们考虑某些假设,这通常无法泛化,总有一天会出现一种与以往攻击行为完全不同的新攻击,这可能会改变一切。
数量、质量与多样性的重要性
这是关于见解,但更具体地说,我们研究了弱防御的数量和质量。在这张图中,你可以看到我们增加了弱防御的数量。在特定轴上,每个点显示了防御模型的测试准确率。蓝线显示了我们在最坏情况下选择这些弱防御的情况(即我们以非常糟糕的方式选择它们,选择了最差的)。这是最佳情况,我们非常仔细地选择了它们。这是随机情况,我们基本上从池中随机选择。
我们观察到,确实,随着良好防御数量的增加,无论怎样,测试准确率都会提高,即使在最坏情况下也是如此。但质量也很重要。你可以看到最坏情况和最佳情况之间存在相当大的差距。因此,弱防御的质量和数量都很重要。
最重要的是,多样性是超级重要的。在这种情况下,我们衡量了来自特定攻击(如“单像素”攻击)的防御的错误率,并将其与两种不同的防御进行比较:一种是基线防御PGD-ADT(这是文献中最好的防御之一,基于对抗训练,已被证明非常有效);另一种是Athena的某个版本,但在这个版本中,为了比较多样性的重要性,我们选择了一个同质的集成模型(即由相同变换的变体组成的集成)。我们看到多样性很重要。多样性的集成样本的错误率远低于同质集成以及最先进的基线防御。随着弱防御数量的增加,这个差距也趋于增大。
大家对此有任何问题吗?因为这很重要。我已经展示了数量、质量以及多样性对于构建这种对抗性防御的重要性。
Athena的架构:弱防御与集成策略
让我们稍微讨论一下架构,特别是我们所说的“弱防御”是什么意思。弱防御只是我们在变换输入上训练的模型的名称。
其工作方式是:我们有一些测试样本。我们使用变换,例如翻转。我们翻转训练集中的所有样本,然后将其输入分类器(可以是神经网络,但不一定是,可以是任何分类器)。然后你构建一个能够分类翻转图像的分类器。对几何变换等其他变换也是如此。你基于变换后的图像构建不同的分类器。
关键原因是,有人可能会问,为什么不使用原始模型?你可以使用,但通常当你翻转或对输入样本进行一些更改时,如果你尝试用原始模型对样本进行分类,也许即使这种变换也可能欺骗模型,这不是我们想要做的。我们不想欺骗自己,我们想要一种能够阻止对抗性示例的机制。
这是设计时的情况。现在这是在部署时防御的工作方式。请确保充分理解这一点,因为它非常重要。在部署时,我们使用几个这样的弱防御,这就是我们所说的集成。我们将几个弱防御放在一起。一旦有输入进入这个防御,会发生的是:它在内部被不同的弱防御进行不同的变换。然后每个变换生成一些标签。正如我们预期的那样,其中一些可能不会产生正确的标签,这实际上是预期的。然后我们有一个集成策略来决定最终标签。
集成策略可以是多数投票,即在这种情况下哪个标签占多数。你可以想出其他集成策略。如果你了解集成机器学习,你可以考虑许多不同类型。在我们的框架中,我们已经实现了一些。例如,对从每个模型获得的概率分布进行平均。如果这些是神经网络,每个都会在softmax之后产生一个概率分布。每个都给你一些概率分布。你通过最大化这个概率分布来获得标签。我们就是这样做的。然后,你可以平均所有这些概率,产生一个最终的概率分布,然后基于这个最终的概率分布决定标签,这可能与多数投票不同。或者你可能想在输入到softmax之前获取激活值,并对这些激活值取平均。这是另一种集成策略。或者你可以使用内部层而不是最后一层,也许你想选择内部层来决定,或者随机选择层。每种都可能是潜在的集成策略。你甚至可以基于过去的行为学习集成策略。所有这些都有可能。
在这个项目中,我们非常期待大家能想出一些有趣甚至扩展这些集成策略的方法。如果你能想出来,你可以获得加分。
评估与威胁模型
一些评估结果。理解这一点很重要。在网络安全研究或任何评估中,你基本上需要考虑一个特定的场景。这非常重要。这些场景被称为威胁模型。
威胁模型是指我们需要考虑对手对模型了解多少的情况。对手对模型内部、防御以及几乎任何事情的了解程度,以及在这种设置下的灵活性。
为了清楚起见,我们在四种不同的假设或威胁模型下进行了评估,在项目中我们也需要做同样的事情。
- 零知识:对手不知道有任何防御措施,不知道弱防御或集成策略,但肯定知道模型的参数。
- 黑盒攻击:对手知道存在防御,但不知道分类器的内部结构或防御的任何内部信息,只知道有防御存在。
- 灰盒:攻击者几乎知道一切,只是不知道集成策略。
- 白盒:知道一切。
这就是我们讨论的假设类型。
一些结果。我们使用FGSM作为攻击之一,使用特定类型的神经网络架构ResNet,数据是CIFAR-10。在这种情况下,如果对手知道关于这个ResNet模型的一切,你可以看到一些错误率结果。这里展示了具有不同集成策略的Athena实例化结果:多数投票、前两名多数投票、输出平均和随机防御。Yin会解释,例如,前两名多数投票是指当我们选择前两个标签并基于此决定时;输出平均是指我们对概率分布或逻辑值(logits)取平均。逻辑值基本上是softmax之前的层激活值。技术上,我们可以对任何激活层的任何输出取平均。你知道,当我们谈论神经网络时,每一层都有一些与层k相关的Z向量。如果你想,你可以使用这些向量中的任何一个。但越接近神经网络末端,可能包含的信息越多,更有意义。对于这些结果,我们使用了逻辑值,即通过softmax之前的层激活值。
基线我们已经知道PGD-ADT(对抗训练)和随机平滑是一种经过认证的防御,也是一种强大的防御。经过认证的防御是指那种你有保证的防御,即无论对手做什么,都无法使防御的错误率超过某个阈值。所以你有一些关于它的认证。
结果还显示,无论扰动类型如何,这种经过认证的防御的有效性将保持不变。结果和这条粉线显示了未防御模型的性能。例如,在这种情况下,你可以看到,对于这个扰动(这是攻击者的参数,表示攻击者有多强大),未防御模型的错误率相当高,大约80%。这些防御的错误率也不那么令人印象深刻。但这是我们防御的错误率,你可以看到大多数这些集成策略表现得相当好,甚至与这些最先进的防御机制相比也是如此。这要归功于多样性和这些变换的质量。
我们在不同类型的零知识攻击中也观察到了类似的情况,结果相似,不想深入细节。


黑盒攻击场景
让我们继续讨论黑盒场景威胁模型。再次澄清,黑盒只知道模型的存在,但对目标模型一无所知。
我们尝试了两种不同的黑盒攻击。第一种是基于可转移性的方法。在这种情况下,对手显然可以近似你的模型。它可以创建一个替代模型来模拟实际模型。这很容易。你可以开始训练一个新模型,给你不知道的模型一些输入,获得一些输出,使用这些输入输出来训练一个新模型(新架构、新内部结构等),然后这个新模型将是一个子模型。然后你可以使用这个子模型,用你之前知道的相同方法(如FGSM)发起对抗性示例,然后希望这些对抗性示例能够转移到实际模型上。“转移”意味着它们可以欺骗目标模型。清楚了吗?
所以,基本上使用一些样本来构建一个模型来模拟这个目标模型。为了真正创建对抗性示例,你需要一个模型,因为你需要计算损失函数。希望您为此模型创建的对抗性示例也能转移回这里。这就是它的工作方式。
这里有一些结果。在Y轴上,你可以看到一些可转移率。在X轴上,你可以看到不同的预算。预算表示对手使用了多少样本来创建你的模型。1K意味着使用1000个样本输入到实际模型,获取输出,并使用相同的样本来构建模拟模型。
有趣的结果是,与未使用Athena的模型相比,当我们使用Athena时,我们能够降低可转移率,这意味着防御降低了对手基于模拟模型生成的示例的可转移性。这是我们想要达到的目标,也是我们从Athena中得到的结果。每个点显示了不同ε值的不同场景。这是针对BIM攻击的,但我们在不同攻击的研究中也看到了相同的情况。
除了这种基于可转移性的场景,我们还测试了一些基于优化的黑盒攻击,特别是“HopSkipJump”,这是2020年出现的一种非常新的攻击。我们再次使用ResNet和CIFAR-10进行了测试,不同的预算水平。这是对抗性示例与原始示例之间的距离。
你可以看到,当我们部署Athena来防御模型时,HopSkipJump需要扰动示例更多才能欺骗模型。这对对手来说很糟糕。看看这个例子,对手能够用这个例子欺骗模型。但是,在Athena部署的情况下,对手需要用这个例子欺骗模型。看看这个,任何人都能看到这是一张失真的图像,这不是真实的。这里有更多的例子。所以基本上,这意味着为了欺骗模型,对手需要扰动更多,这很糟糕,因为任何检测机制都可以识别出来自对手的输入。这不是对手想要的。
对此有任何问题吗?
白盒攻击场景与扩展性
让我们继续讨论白盒场景。这基本上是攻击者几乎了解模型和防御的一切的情况。显然,在这种情况下,任何防御都没有机会,因为攻击者几乎知道一切。因此,我们预计即使是防御模型也会出现高错误率,这正是我们得到的结果。
这是Athena的性能,意味着错误率随着最大归一化相似度的增加而增加。这是扰动图像和正常图像之间的差异,在所有示例上取平均。你可以看到,随着这个值的增加,防御模型的错误率也随之增加。所以错误率相当高,这是我们预期看到的。
但当我们看例子时,我提到这并不容易。例如,我们做的一个测试是使用检测机制。你可以看到,在这里,随着对手强度的增加,准确率会大幅下降。但我们训练了一个检测机制,并能够在甚至输入到系统之前,就将这些示例标记为对抗性示例。从例子中可以明显看出,这些显然来自对抗性攻击。因此,通过后检测和防御机制的混合,我们能够构建一个相对较好的防御机制,在不同对手强度下保持准确率。
除此之外,它还带来了一些成本。生成一个示例所需的时间随着强度的增加而增加。这意味着,如果它想生成大量示例,需要很长时间。当我们谈论实时场景时,对手没有足够的时间实时欺骗系统,所以时间很重要,因为对手没有太多时间生成这些示例,在短时间内不可能在某些场景下生成。
此外,Athena再次回到我们没有对攻击者做任何假设的情况。它也很通用,意味着我们可以改变讨论的ResNet架构。但我们可以改变架构类型。分类器类型可以改为SVM或随机森林,我们能够在某种程度上得到类似的结果,但有一些细微差别。例如,我们将架构更改为带有FGSM的ResNet和CIFAR-10数据集。再次,不同的集成策略。顺便说一下,每个点代表其中一个弱防御的性能。结果或多或少相似,程度不同。
对于不同类型的攻击,我们也尝试了SVM模型。对于SVM,在某些情况下,未防御模型和防御模型之间的差异很小,所以基本上在某些场景下,它没有像以前那样显示出好处,但仍然比未防御模型好一点。这表明它在不同类型的分类器中具有相当的通用性,并不非常特定于深度神经网络,可以扩展到其他类型的分类。
成本与总结

另外,显然它带来了成本
5.3:Athena框架演示与课程项目概述 🎯

在本节课中,我们将一起学习USC《机器学习系统》课程中关于Athena框架的演示,并详细了解课程项目的具体要求。我们将从项目概述开始,逐步深入到任务细节、代码结构以及如何开始你的项目。
项目概述与要求 📋
首先,我们来了解项目的整体框架和基本要求。本项目围绕对抗性机器学习展开,你将有机会深入探索攻击与防御策略。
项目核心要求
以下是项目的基本要求,请务必仔细阅读。
- 任务结构:项目分为三个主要任务。任务一和任务二各有三周时间完成,任务三为交叉评估与分析。
- 额外加分:对于任务一和任务二,任何Athena框架未实现的新方法,只要被成功实施并分析,都可以获得额外加分。
- 报告撰写:报告需要以科学的方式进行撰写。对于你实施的新方法,需要进行简明扼要的介绍,说明其工作原理,并附上核心公式或伪代码。
- 结果对比:你需要将你的方法(无论是攻击还是防御)与课程提供的基线进行对比。基线包括我们生成的对抗样本、Athena的原始版本以及PGD训练模型。
重要提示:我们更关注你从结果中获得的洞察,而非结果本身的好坏。即使你的方法效果不佳,只要你能深入分析原因,并提出有见地的解释,同样可以获得高分。
任务二:深入探索与选项详解 🛠️


上一节我们介绍了项目的整体要求,本节中我们来看看任务二的具体选项。任务二提供了四个不同的方向,每个小组需要选择其中一个并在Piazza上声明。
任务二选项列表
以下是任务二的四个选项及其简要说明。
- 选项一:白盒攻击者任务:在此选项中,你需要在白盒威胁模型下生成对抗样本。攻击者知晓Athena框架的一切细节,包括所有弱防御和集成策略。你可以使用我们提供的两种基于优化的攻击方法,也可以尝试新的攻击方法以获得加分。
- 选项二:基于学习的集成策略:当前Athena使用固定的集成策略(如多数投票)。此选项要求你训练一个机器学习模型,该模型以弱防御的输出作为输入,并生成最终的预测标签。这是一个更具创造性的挑战。
- 选项三:概率化Athena:当前Athena的弱防御是确定的CNN模型。此选项要求你尝试构建概率化的分类器(例如贝叶斯神经网络)作为弱防御,然后构建集成模型。
- 选项四:混合集成:当前Athena仅使用CNN模型构建集成。此选项我们将提供SVM和CNN模型。你需要设计策略从一个大的模型池中选择一个子集来构建集成防御。
关于“基于学习的集成策略”的进一步说明
这个选项可能有些抽象,让我们进一步解释其目标。在Athena中,集成策略是固定的。我们希望通过监督学习,从训练数据中学习出一个更好的集成策略。
核心思想:每个弱防御都会输出一个概率分布。集成器的目标就是利用这N个概率分布信息,生成一个最终的输出概率分布。在抽象层面上,这可以看作是一个加权平均问题。学习型集成策略的任务就是:学习最优的权重向量 w = [w1, w2, ..., wn],使得加权后的概率分布能做出最佳决策。
你可以查阅集成学习(Ensemble Learning)的相关文献,寻找灵感。首先,你需要确定如何形式化这个学习问题,然后选择一种现有的集成方法进行尝试和应用。无论结果好坏,只要过程清晰、分析深入,都值得在报告中详细阐述。
评估、实验与代码框架 💻
在明确了任务选项后,我们需要了解如何评估我们的方法,以及项目提供的代码框架如何帮助我们快速上手。
评估要求


对于你构建的任何防御或生成的对抗样本,我们期望你创建多个变体进行评估。
- 对于防御:例如,你可以改变集成防御中弱防御的数量(如3个、10个),观察集成规模对防御效果的影响。
- 对比基线:你需要使用生成的对抗样本和提供的基线对抗样本来评估你的防御方法,并与两个基线模型(原始模型、PGD训练模型)进行对比。
- 数据集说明:项目主要基于MNIST数据集。虽然鼓励在CIFAR-10上尝试,但请注意其更大的计算开销。如果使用CIFAR-10的子集,请确保采样均衡,避免引入偏差。





代码框架导览
项目代码已发布在GitHub仓库中。整个框架基于PyTorch构建。以下是核心目录和模块的简要介绍。
utils/:工具函数目录,包含数据加载、结果保存(JSON/CSV)和错误率计算等实用功能。models/:模型定义目录,这是最重要的部分。weak_defense.py:定义了弱防御的包装类,可以将TensorFlow/Keras或PyTorch模型包装成Athena可用的弱防御。athena.py:定义了集成模型(Ensemble类)。构建集成时需要提供一个弱防御列表和集成策略。
attacks/:攻击方法目录,实现了如FGSM等攻击算法。主要入口是generate函数。config/:配置文件目录,以JSON格式定义了Athena的弱防御配置、数据路径和模型路径。scripts/:实验脚本目录,你可以在这里编写训练、攻击生成等实验代码。
快速开始示例
以下是如何使用该框架进行关键操作的核心代码片段。
1. 加载弱防御池并构建集成:
# 加载配置
config = load_from_json(‘config/athena-mnist.json‘)
# 加载弱防御池
pool = load_pool(config)
# 第一个是未防御的原始模型,将其取出
undefended_model = pool.pop(0)
# 用剩余的弱防御构建集成,使用“平均概率”策略
ensemble_model = Ensemble(classifiers=pool, strategy=‘avg_prob‘)
2. 生成对抗样本:
# 定义攻击配置(例如FGSM)
attack_config = {‘type‘: ‘FGSM‘, ‘eps‘: 0.3}
# 在零知识威胁模型下攻击原始模型
ae_zero_knowledge = generate(model=undefended_model, data_loader=test_loader, attack_args=attack_config)
# 在白盒威胁模型下攻击集成模型
ae_white_box = generate(model=ensemble_model, data_loader=test_loader, attack_args=attack_config)
如你所见,通过更换generate函数中的目标模型,你可以轻松地在不同威胁模型下生成对抗样本,这对应了任务一和任务二(选项一)的核心工作。
任务三、协作与总结 📝
最后,我们来了解项目收尾阶段的任务以及团队协作的注意事项。

任务三:交叉评估与分析
任务三将在所有小组提交任务一和任务二的结果后开始。助教将运行一个交叉评估脚本,评估所有小组生成的对抗样本(AEs)和防御模型。评估结果将公开发布。
你的任务是基于这些交叉评估的结果进行深入分析。例如,尝试解释为什么某个小组生成的对抗样本能够成功欺骗另一个小组构建的防御。这部分主要是分析和报告撰写工作。
团队协作与提交
请使用GitHub进行团队协作和代码管理。

- 仓库管理:在团队确定后,你们将被添加到课程组织,并可以创建自己的项目仓库。请频繁提交代码。
- 问题跟踪:利用GitHub的Issue和Projects功能来管理项目任务和进度,这非常有助于团队协作。
- 沟通:如有关于项目描述的澄清或改进建议,请在GitHub仓库中创建Issue。关于课程的一般性问题,则使用Piazza。
课程总结 🎓
本节课中我们一起学习了CSCE585课程项目的完整概览。我们详细解读了项目的三个核心任务,特别是任务二中四个富有挑战性的选项。我们熟悉了Athena对抗性机器学习框架的代码结构,并了解了如何利用它快速开始攻击与防御实验。最后,我们明确了项目评估标准、团队协作方式以及报告撰写的要点。

记住,本项目鼓励创造性和深入的分析。不要过于担心分数,而是专注于探索对抗性机器学习这一激动人心的领域,尝试带来令人惊喜的发现。祝你们项目顺利!
6.1:机器学习系统栈 🏗️

在本节课中,我们将要学习机器学习系统的实现栈。我们将从高层次概述开始,探讨现代机器学习框架背后的核心原则,并通过对比手动实现与框架辅助实现,理解自动微分等关键概念带来的便利。
概述
在之前的课程中,我们讨论了机器学习系统的高层概述、相关挑战、DevOps工具以及分类器的基础理论。现在,我们自然要问:如何将这些理论付诸实践?本讲将介绍机器学习系统的实现栈,帮助我们理解各种流行框架(如TensorFlow、PyTorch)背后的设计原则,以便根据系统需求和约束(例如部署在移动设备上)做出明智的选择。
系统栈的分层视图
要理解深度学习或更广泛的机器学习系统栈,不应只考虑单层,而应考虑多个代码层。每一层负责实现的不同方面。
- 高层用户API:你可能需要一些API或库来计算梯度,因为梯度计算是所有机器学习模型或系统优化任务中最基本、最核心的功能。
- 中层:你可能需要进行计算图优化与执行。我们将讨论其含义和好处,以及基于这些计算图的运行时并行调度。现代框架背后的关键创新思想正是计算图模型,而非临时性的计算方式。我们将在课程后面部分讨论为何需要这种计算图及其好处。这一层是为机器学习系统计算提供诸多好处的最基础层。
- 底层:更接近硬件,你需要一些内核代码、优化工具和加速器。这些与硬件紧密相关且是领域特定的。
越接近栈的底层,领域特定性越强;越接近高层,通用性越强。这种分层设计的目标是方便最终用户。因为以前如果有人想开发机器学习系统,需要自己实现所有这些部分,成本高昂。他们可能需要为特定的硬件平台实现优化代码,或者以临时、非原则性的方式完成所有工作。这些框架的好处在于提供了清晰的层间边界,通过这种分层设计,你可以真正以原则性的方式构建系统。
从手动实现到框架辅助
上一节我们介绍了系统栈的分层概念,本节中我们来看看具体的代码实现差异。我们将以逻辑回归模型为例,对比使用NumPy手动实现与使用TensorFlow类框架实现的区别。
使用NumPy手动实现逻辑回归
以下是一个使用NumPy实现逻辑回归模型训练循环的简化示例。代码定义了模型、所需计算以及优化过程(通过找到能正确分类样本的合适权重)。
import numpy as np
def softmax(x):
# 避免数值溢出的技巧:减去最大值
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
# 模型参数初始化
W = np.zeros((input_size, num_classes))
learning_rate = 0.01
# 训练循环(简化的SGD)
for epoch in range(num_epochs):
# 假设 batch_xs 和 batch_ys 是一个批次的训练数据和标签
scores = np.dot(batch_xs, W)
probs = softmax(scores) # 模型预测的概率
# 计算梯度(针对交叉熵损失和sigmoid激活的特定推导)
dscores = probs - batch_ys # 真实标签与预测概率之差
dW = np.dot(batch_xs.T, dscores) # 损失对权重的梯度
# 参数更新(梯度下降)
W -= learning_rate * dW
代码关键点分析:
- Softmax实现:代码中的
softmax函数包含一步预处理x - np.max(x)。这样做是为了避免指数运算时可能发生的数值溢出(当x中的值很大时)。从数学上可以证明,对输入向量中的所有元素减去同一个值(通常是最大值),不会改变softmax函数的输出结果。这是一种常见且重要的数值稳定化技巧。 - 梯度计算:梯度
dW的计算公式是np.dot(batch_xs.T, dscores)。这里的dscores是模型预测概率与真实标签(one-hot编码)的差值。这个特定的梯度形式来源于交叉熵损失函数和sigmoid激活函数(或softmax的多类推广)联合推导的结果。如果改变损失函数或激活函数,这个计算梯度的代码行就需要重新推导并修改。 - 手动求导:整个梯度计算过程需要开发者根据所选损失函数和模型结构,手动进行数学推导并编码实现。这对于复杂的模型来说容易出错且不灵活。
使用TensorFlow类框架实现
以下是使用类似TensorFlow的框架(此处称为TinyFlow)实现相同功能的示例。框架的核心优势之一是提供自动微分。
import tensorflow as tf
# 定义计算图:声明变量和操作
x = tf.placeholder(tf.float32, [None, input_size])
y_true = tf.placeholder(tf.float32, [None, num_classes])
W = tf.Variable(tf.zeros([input_size, num_classes]))
scores = tf.matmul(x, W)
probs = tf.nn.softmax(scores)
# 定义损失函数(无需手动推导梯度形式)
loss = tf.reduce_mean(-tf.reduce_sum(y_true * tf.log(probs), axis=1))
# 自动计算损失函数关于所有变量的梯度
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train_op = optimizer.minimize(loss)
# 执行计算图
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(num_epochs):
# feed_dict 喂入 batch_xs 和 batch_ys
sess.run(train_op, feed_dict={x: batch_xs, y_true: batch_ys})
框架优势分析:
- 声明式编程:首先定义计算图(变量、操作、损失函数),然后执行它。这与命令式编程(如NumPy)不同。
- 自动微分:开发者只需定义前向传播的损失函数
loss,框架的optimizer.minimize(loss)会自动计算损失函数关于所有可训练变量(如W)的梯度。无需手动推导dW = np.dot(batch_xs.T, dscores)这样的公式。 - 灵活性与可维护性:更改损失函数(例如换成均方误差)或激活函数时,只需修改一两行定义代码,梯度计算会自动更新,无需重写复杂的求导代码。
手动求导的数学原理(以交叉熵损失为例)
为了理解框架自动微分的好处,我们简要回顾一下为何手动实现中的梯度是那个形式。考虑二分类逻辑回归,sigmoid激活函数为 σ(z) = 1/(1+e^{-z}),其中 z = w·x。交叉熵损失函数为:
J(w) = - (1/m) Σ [ y_i * log(σ(z_i)) + (1-y_i) * log(1-σ(z_i)) ]
我们的目标是求梯度 ∇J(w),即对某个权重 w_j 求偏导 ∂J/∂w_j。运用链式法则:


∂J/∂w_j = (∂J/∂σ) * (∂σ/∂z) * (∂z/∂w_j)- 经过推导(过程略),最终可简化为:
∂J/∂w_j = (1/m) Σ (σ(z_i) - y_i) * x_ij


这正是手动代码中 dscores = probs - batch_ys 与 dW = np.dot(batch_xs.T, dscores) 的数学依据。可以看到,即使对于相对简单的模型,求导过程也需要一系列严谨的数学运算。对于深度神经网络,手动求导将变得极其复杂且容易出错。
总结

本节课中我们一起学习了机器学习系统的实现栈。我们了解到一个完整的系统栈包含高层API、计算图优化层和底层硬件抽象层。通过对比使用NumPy手动实现逻辑回归与使用TensorFlow类框架的实现,我们清晰地看到了框架的核心价值:自动微分。它使开发者从繁琐且易错的梯度公式手动推导中解放出来,只需关注模型结构和损失函数的定义,从而大幅提高了开发效率、代码的灵活性和可维护性。理解这些底层原理有助于我们在实际项目中根据具体需求(如部署环境、社区支持、开发效率)选择合适的框架。
6.2:计算图 🧮

在本节课中,我们将深入探讨机器学习系统栈中的一个核心概念:计算图。我们将学习计算图是什么,如何构建它,以及它如何为机器学习模型的优化(特别是梯度计算)提供强大的支持。
概述
上一节我们介绍了机器学习系统的整体栈结构。本节中,我们将聚焦于栈中更深的一层:计算图。计算图是一种新颖的、用于表示数学表达式的现代方法。它提供了一系列优化机会,尤其是在并行计算和自动微分方面,这与编译器在传统软件系统中进行的优化有相似之处。我们将通过简单的例子,直观地理解计算图的结构和优势,并最终将其与机器学习模型(如逻辑回归、神经网络)的梯度计算联系起来。
什么是计算图?
计算图是一种用于表示数学表达式的有向无环图。图中的节点代表计算操作,边代表数据依赖关系。
让我们从一个简单的数学表达式开始构建计算图。假设我们有如下表达式:
E = (A + B) * (B + 2)
我们可以通过引入中间变量来分解这个表达式:
C = A + BD = B + 2E = C * D
基于这个结构,我们可以构建一个计算图:

以下是图中各元素的含义:
- 节点:代表一个简单的计算操作(例如加法、乘法)。
- 边:代表数据依赖关系,连接着计算所需的输入和输出。
这种图形化表示使得表达式中的结构变得清晰可见。
计算图的优势
计算图的这种结构带来了几个重要的优化机会。以下是其主要优势:
- 避免重复计算:每个节点只计算一次。例如,在计算
D时,我们不需要为B重复计算。 - 显式计算顺序:计算之间的依赖关系非常明确,避免了操作顺序的歧义。
- 并行计算机会:独立的计算路径可以同时进行。例如,计算
C和D是相互独立的,可以在不同的处理器核心上并行执行。对于复杂的表达式,这种并行化潜力可以极大地提升计算效率。 - 高效的梯度计算:计算图为实现反向传播算法提供了天然框架,允许我们通过一次反向遍历,高效地计算出输出相对于所有输入变量的梯度。这对于优化拥有数百万参数的机器学习模型至关重要。
前向传播与梯度计算
为了理解计算图如何帮助计算梯度,我们先尝试“前向”计算梯度,即从输入变量开始,向输出方向传播。
假设我们想计算 E 相对于 B 的偏导数 ∂E/∂B。我们可以从输入开始,一步步计算中间变量的导数。
首先,计算直接导数:
∂A/∂B = 0∂B/∂B = 1
接着,利用链式法则计算中间节点:
C = A + B,所以∂C/∂B = ∂A/∂B + ∂B/∂B = 0 + 1 = 1D = B + 2,所以∂D/∂B = ∂B/∂B + ∂2/∂B = 1 + 0 = 1
最后,计算 E = C * D。这里需要用到乘积法则:
∂E/∂B = (∂E/∂C) * (∂C/∂B) + (∂E/∂D) * (∂D/∂B)
其中:
∂E/∂C = D∂E/∂D = C
因此,∂E/∂B = D * 1 + C * 1 = D + C。
如果我们预先知道 A=3, B=5,那么 C=8, D=7,最终 ∂E/∂B = 7 + 8 = 15。
前向传播的局限性:这种方法在计算 ∂E/∂B 时是有效的。但如果我们需要计算 E 相对于另一个输入 A 的梯度 ∂E/∂A,我们几乎需要从头开始重复整个计算过程。当模型有大量参数(例如W1, W2, … Wn)时,这种逐个计算梯度的方法效率极低。
反向传播:一次遍历,全部梯度
反向传播解决了上述效率问题。其核心思想是:从输出节点(例如损失 E)开始,反向遍历计算图,利用链式法则,一次性计算出输出相对于所有输入变量的梯度。
让我们在计算图上进行反向传播。我们首先为每条边标注一个简单的“局部梯度”,即子节点相对于父节点的偏导数。这些局部梯度通常很容易计算。
∂C/∂A = 1,∂C/∂B = 1∂D/∂B = 1∂E/∂C = D,∂E/∂D = C
现在,我们从输出 E 开始反向计算:
- 目标:计算
∂E/∂E = 1。 - 计算
∂E/∂C和∂E/∂D:我们已经有了这些局部梯度(D和C)。在反向传播中,它们被称为从E流向C和D的“梯度”。 - 计算
∂E/∂A:从E到A只有一条路径:E <- C <- A。根据链式法则,∂E/∂A = (∂E/∂C) * (∂C/∂A)。我们只需将路径上的梯度相乘:D * 1 = D。 - 计算
∂E/∂B:从E到B有两条路径:- 路径1:
E <- C <- B:梯度为(∂E/∂C) * (∂C/∂B) = D * 1 - 路径2:
E <- D <- B:梯度为(∂E/∂D) * (∂D/∂B) = C * 1
根据多元链式法则,总梯度是各路径梯度之和:∂E/∂B = D + C。
- 路径1:
关键洞察:通过这一次反向遍历,我们同时得到了 ∂E/∂A 和 ∂E/∂B。如果图中有百万个输入节点,也只需这一次遍历即可获得所有梯度。这正是训练深度神经网络时反向传播算法高效的原因。
与机器学习模型的联系
现在,让我们将计算图的概念与机器学习模型联系起来。以逻辑回归为例:
一个简单的逻辑回归模型可以表示为:
预测 = σ(W1*X1 + W2*X2 + B),其中 σ 是sigmoid函数。
其计算图可以构建如下:
- 输入节点:
X1,X2,W1,W2,B - 计算节点:
Z = W1*X1 + W2*X2 + B - 计算节点:
A = σ(Z) - 输出/损失节点:
L = Loss(A, 真实标签)
在训练时,我们需要计算损失 L 相对于所有参数(W1, W2, B)的梯度,以使用随机梯度下降进行更新。这正是反向传播的用武之地:
- 前向传播:计算图中,从输入到损失
L,计算预测值和损失。 - 反向传播:从损失
L开始,反向遍历计算图,应用链式法则,一次性计算出∂L/∂W1,∂L/∂W2,∂L/∂B。
对于具有数百万参数的深度神经网络,计算图可能极其庞大和复杂,但反向传播的原理不变,使其能够高效地计算所有梯度。
总结
本节课中,我们一起学习了机器学习系统中的核心组件——计算图。
我们首先了解了计算图是一种有向无环图,它用节点表示计算,用边表示数据依赖。通过一个简单例子,我们看到了如何将数学表达式转化为计算图。
接着,我们探讨了计算图带来的核心优势:避免重复计算、显式依赖关系、发掘并行计算机会,以及最重要的——实现高效的反向传播。
我们详细对比了前向梯度计算的局限性(需为每个输入单独计算)和反向传播的强大之处(一次遍历获得所有输入梯度)。反向传播通过利用计算图的链式结构和预先计算的局部梯度,实现了极高的计算效率。

最后,我们将计算图与逻辑回归等机器学习模型联系起来,明确了它在模型训练(优化损失函数)中扮演的关键角色。理解计算图是理解现代机器学习系统如何高效训练复杂模型的基础。
6.3:机器学习系统栈 🏗️

在本节课中,我们将要学习机器学习系统的整体架构,即“系统栈”。我们将从计算图的概念出发,逐步向下探索系统层和硬件层的重要性,了解整个栈如何协同工作以高效地训练和部署机器学习模型。
从计算图到系统栈
上一节我们介绍了机器学习框架(如 TensorFlow)提供的 API 层,并深入讨论了所有主流框架共有的核心概念:计算图。我们了解了计算图对于机器学习的重要性,特别是在高效计算梯度以进行反向传播和参数优化方面的优势。
本节中,我们来看看计算图之下的系统层和硬件层,以获得对机器学习系统栈的高层次概览和直观理解。
计算图回顾与代码对应
计算图的核心思想是将计算过程表示为有向无环图。图中的节点代表变量或操作,边代表数据流。
以下是一个简单的逻辑回归模型在 TensorFlow 中的代码片段及其对应的计算图概念:
# TensorFlow 代码示例
import tensorflow as tf
# 定义变量(对应计算图中的叶子节点)
X = tf.placeholder(tf.float32, shape=[None, 784]) # 输入
W = tf.Variable(tf.zeros([784, 10])) # 权重参数
b = tf.Variable(tf.zeros([10])) # 偏置参数
# 定义操作(对应计算图中的中间节点和边)
logits = tf.matmul(X, W) + b
Y_pred = tf.nn.softmax(logits)
# 定义损失函数
Y_true = tf.placeholder(tf.float32, shape=[None, 10])
cross_entropy = -tf.reduce_mean(Y_true * tf.log(Y_pred))
# 自动计算梯度(框架自动构建反向计算图)
grads = tf.gradients(cross_entropy, [W, b])
# 定义参数更新操作
learning_rate = 0.01
update_W = W.assign(W - learning_rate * grads[0])
update_b = b.assign(b - learning_rate * grads[1])
这段代码声明了一个计算图。X、W、b、Y_true 是输入/变量节点。tf.matmul、tf.nn.softmax、tf.log、tf.reduce_mean 等是操作节点。tf.gradients 会自动构建计算损失函数相对于 W 和 b 梯度的子图。整个计算过程采用惰性求值,只有在 session.run() 被调用时,指定的部分计算图才会真正执行。
系统层的优化机会
将计算表示为图结构,为系统层面带来了多种优化可能性。这些优化对于大规模模型和部署至关重要。
以下是几种关键的系统级优化:
-
并行执行:计算图可以清晰地展示操作之间的依赖关系。没有依赖关系的操作可以并行执行,从而加速计算。
- 示例:在计算
B = A + 1和C = A * 2时,B和C都只依赖于A,彼此独立,因此可以同时计算。
- 示例:在计算
-
图简化:编译器或图优化工具可以分析计算图,移除无效代码(死代码消除)、合并重复操作或简化计算步骤,生成更高效但等价的图。
-
内存规划:对于大型计算图,高效地管理张量的内存分配和释放是关键。系统可以分析张量的生命周期,复用内存,减少总体内存占用。
-
操作符放置:在异构计算环境(如多GPU、CPU+GPU、分布式集群)中,系统需要决定将每个计算操作放在哪个设备上执行,以最小化通信开销并最大化计算吞吐量。
硬件抽象与编译器方法
传统的机器学习栈需要为每种硬件(如 CPU 的 MKL 库、NVIDIA GPU 的 CUDA、Google TPU 的专用库)编写特定的底层代码,这导致了碎片化和高昂的移植成本。
现代的趋势是引入一个编译器中间层。这个层的目标是将优化后的高级计算图,编译成能在多种硬件后端上高效执行的低级代码。
其优势在于:
- 可移植性:当新的硬件出现时,只需为该编译器添加一个新的后端,上层代码和优化即可复用。
- 统一优化:许多硬件无关的优化(如图简化、算子融合)可以在这一层统一完成。
- 性能:编译器可以进行深入的、针对特定硬件的代码生成和优化。
总结
本节课中我们一起学习了机器学习系统栈的完整视图。
- 我们从顶层的编程接口和计算图开始,理解了它如何抽象计算并自动求导。
- 然后,我们探讨了系统层如何利用计算图进行并行化、内存优化、图简化等操作,这是提升效率的关键。
- 最后,我们了解了底层的硬件抽象和编译器层,它负责将优化后的计算图高效地映射到各种硬件(CPU、GPU、TPU等)上执行。

这个分层架构使得机器学习开发变得高效且可扩展:研究人员可以在顶层快速建模和实验,而系统工程师可以在下层不断优化性能和可部署性。理解这个栈的每一层及其相互作用,是构建和优化现代机器学习系统的基础。
7.1:卷积神经网络 🧠

在本节课中,我们将要学习一种特殊且非常流行的神经网络架构——卷积神经网络。我们将重点理解其背后的两个核心概念,并探讨它们为何在系统与硬件层面具有重要的优化意义。
概述
卷积神经网络之所以重要,是因为它们在实践中被广泛部署。理解其核心概念,有助于我们把握在系统和硬件层面进行优化的机会,例如内存优化、模型压缩以及在资源受限平台上的高效部署。
卷积神经网络的两个核心概念
卷积神经网络主要基于两个关键思想:稀疏连接和参数共享。这两个概念使其能够高效处理大规模的、结构化的输入数据(如图像、语音、视频)。
稀疏连接
上一节我们提到了神经网络,在传统的全连接深度神经网络中,每个输入单元都连接到下一层的所有神经元。
代码描述:
对于一个有5个输入的层,其输出 S5 的计算可能如下:
S5 = w1*x1 + w2*x2 + w3*x3 + w4*x4 + w5*x5
可以看到,输入 x3 影响了所有输出单元的值。
然而,在卷积神经网络中,我们采用了稀疏连接。这意味着每个输入单元只影响下一层中有限数量的神经元,而不是全部。
原因:
这种设计极大地减少了需要存储和计算的参数量,从而提升了效率。同时,在数据量相同的情况下,学习更少的参数通常能获得更高的统计置信度。
过渡:
理解了稀疏连接如何减少连接数量后,我们来看看它是如何通过“感受野”的概念来保证网络仍能“看到”完整输入的。
感受野
你可能会问,稀疏连接是否会导致某些输入信息被忽略?在单层网络中,确实如此。但在深层架构中,通过叠加多个隐藏层,高层神经元能够间接“看到”更广阔的输入区域,这个区域就是它的“感受野”。随着网络加深,感受野会不断扩大,最终使高层神经元能够整合整个输入空间的信息。
参数共享
参数共享是卷积神经网络的另一个核心思想。在全连接网络中,每条连接都有其独立的权重参数需要学习。
公式描述:
全连接层中,权重矩阵 W 的每个元素 W_ij 通常是独立且不同的。
但在卷积操作中,我们使用一个称为“卷积核”或“滤波器”的小型权重矩阵,在输入数据上滑动。这个卷积核的参数在整个滑动过程中是共享的。
原因:
- 极大减少参数量:无论输入多大,一个卷积核只对应一组参数(例如一个3x3的核只有9个参数)。
- 提升泛化能力:用相同的数据学习更少的参数,模型学到的特征更具统计鲁棒性。
- 引入平移不变性:相同的特征检测器(卷积核)扫描整个图像,使得无论目标出现在图像中的哪个位置,都能被识别出来。
过渡:
现在,让我们具体看看实现稀疏连接和参数共享的关键数学操作——卷积。
卷积操作
卷积可以看作是全连接网络中矩阵乘法的一种替代。引入这个新操作后,网络训练过程(如最大似然估计、反向传播)保持不变,只是前向传播的计算方式发生了改变。
什么是卷积?
想象你有一张大的输入图像(一个网格)和一个小得多的卷积核(另一个网格)。卷积操作就是将这个卷积核像“窗口”一样滑过整个输入图像。
计算过程:
在每一个位置,将卷积核覆盖的输入区域与卷积核的对应元素相乘,然后将所有乘积结果求和,得到一个输出值。接着滑动窗口,重复此过程,最终生成一个新的输出网格,这个输出通常被称为“特征图”。
示例:
假设输入网格为 [A, B, C; D, E, F; G, H, I],卷积核为 [W, X; Y, Z]。
在左上角位置,输出 O1 = A*W + B*X + D*Y + E*Z。
将核向右滑动一格,输出 O2 = B*W + C*X + E*Y + F*Z,依此类推。
多通道与特征图
对于彩色图像(RGB三通道),输入可以看作三个叠加的网格。卷积核也会具有相应的深度。每个卷积核会产生一个二维的特征图。如果我们希望网络学习多种不同的特征(如边缘、纹理),就可以使用多个不同的卷积核,从而产生多个特征图(通道)。通常,网络越深,特征图的通道数会越多,学习到的特征也越高级。
过渡:
了解了前向传播的卷积操作后,我们简要回顾一下训练中至关重要的反向传播在卷积网络中是如何进行的。
反向传播与卷积
在全连接网络中,反向传播涉及矩阵乘法和矩阵转置。在卷积网络中,原理完全相同,只是将矩阵乘法替换为卷积运算。具体而言:
- 前向传播:使用卷积操作。
- 反向传播(误差传递):使用一种称为“转置卷积”的操作(本质也是卷积)将误差从输出层传回输入层。
- 权重梯度计算:同样利用卷积操作来计算每个卷积核参数的梯度,以便通过随机梯度下降法进行更新。
框架(如TensorFlow、PyTorch)会自动处理这些复杂的计算。
效率对比示例
让我们直观感受一下卷积网络在参数效率上的优势。
考虑一个320x280的输入图像。如果我们使用一个2x1的卷积核,我们需要存储和学习的参数是多少?仅仅是卷积核的2个权重(w1, w2)。
思考:
如果使用一个等效的全连接层(假设输出特征图大小约为319x280),需要多少参数?那将需要 (320*280) * (319*280) 数量级的参数,这是一个天文数字。卷积网络的稀疏连接和参数共享使得处理大规模图像成为可能。
总结

本节课中,我们一起学习了卷积神经网络的基础:
- 核心思想:稀疏连接减少了神经元间的连接数量;参数共享(通过卷积核)极大地降低了参数量。
- 关键操作:卷积是滑动滤波器以提取局部特征的过程,它是构建CNN的基础。
- 主要优势:这些特性带来了计算和存储的高效性、更好的参数统计学习效率,以及固有的平移不变性,使CNN特别适合处理图像等网格化结构数据。
理解这些概念是后续探讨CNN在系统优化、硬件加速及模型压缩等方面应用的重要基础。
CSCE585:ATHENA框架项目答疑与教程 🎓

在本节课中,我们将回顾USC《机器学习系统》课程中关于ATHENA对抗性防御框架的项目讨论。我们将重点关注项目任务一的核心步骤:生成对抗样本、测试模型性能以及理解整体工作流程。课程助教Ying将提供代码层面的详细指导。
项目概述与常见问题
在深入细节之前,我们先讨论一下同学们在项目中遇到的普遍问题。许多来自非计算机科学背景的同学在环境配置和代码理解上遇到了挑战。
一个关键建议是组建跨学科团队,让计算机科学专业的同学与其他专业的同学合作,这样可以更好地相互学习。助教团队也乐意协助解决安装和初始设置问题。
核心工作流程概念:
- 你有一个训练好的模型(例如,一个分类器)。
- 你有一组良性测试样本(例如,CIFAR-10测试集)。
- 你使用攻击算法(如FGSM、CW)生成这些良性样本对应的对抗样本。
- 你将对抗样本输入到不同的模型(无防御模型、ATHENA防御模型、PGD-ADT防御模型)中进行预测。
- 你将模型的预测结果与真实标签进行比较,计算错误率等指标。
任务一详解:生成与测试对抗样本
上一节我们概述了项目的整体目标,本节中我们来看看任务一的具体实现步骤。我们将学习如何使用ATHENA代码库生成对抗样本,并计算模型在对抗样本上的错误率。
生成对抗样本
以下是使用FGSM攻击生成对抗样本的核心代码示例。代码位于 craft_adversarial_examples.py 文件中。
# 导入必要的库和攻击模块
from art.attacks.evasion import FastGradientMethod
# ... 其他导入
# 加载模型和数据
model = ... # 加载你的模型
(x_test, y_test) = ... # 加载测试数据和标签
# 创建攻击实例
attack = FastGradientMethod(estimator=classifier, eps=0.25)

# 生成对抗样本
x_test_adv = attack.generate(x=x_test)

测试模型性能
生成对抗样本后,下一步是测试目标模型(无论是无防御模型还是ATHENA防御模型)在这些样本上的性能。这涉及到使用模型进行预测,并将预测结果与真实标签对比。
以下是如何计算错误率的步骤:
- 获取模型预测:将对抗样本输入模型,获得预测结果(通常是每个类别的概率向量)。
- 计算错误率:比较预测标签与真实标签,统计分类错误的样本比例。

代码中提供了一个辅助函数 error_rate 来简化计算。
# 导入错误率计算函数
from utils.metrics import error_rate
# 使用模型对对抗样本进行预测
predictions = model.predict(x_test_adv)
# 计算错误率
err = error_rate(predictions, y_test)
print(f"Adversarial Error Rate: {err}")
公式解释:
错误率的基本计算公式为:
错误率 = (错误分类的样本数) / (总样本数)
使用不同模型进行测试
任务要求测试三种模型:原始无防御模型、ATHENA集成防御模型和PGD-ADT防御模型。它们的测试流程是相似的。
以下是关键步骤:
- 加载模型:从提供的文件或配置中加载相应的模型。
- 进行预测:调用模型的
predict方法。 - 计算指标:使用相同的
error_rate函数计算性能指标。
对于ATHENA模型,其配置通常在JSON文件中定义,指定了集成中包含哪些弱防御器及其参数。
项目答疑与实用建议
在理解了基本流程后,我们来看看同学们提出的具体问题及其解决方案。
环境配置问题
许多安装问题源于依赖库冲突或路径设置错误。
解决方案:
- 严格遵循
notebooks/setup.md文件中的最新说明。 - 确保使用了更新后的
requirements.txt文件。 - 如果遇到特定错误(如TensorFlow找不到),请尝试在问题追踪系统中详细描述错误信息、操作系统和已尝试的步骤,以便助教复现和解决。
对抗样本生成相关问题
有同学发现某些攻击(如DeepFool、JSMA)生成的对抗样本视觉变化不明显,且模型总能正确分类。
可能原因与检查项:
- 像素值范围:确保输入图像的像素值被归一化到
[0, 1]区间。ATHENA框架内部通常进行此处理。 - 攻击参数:不同攻击算法对参数敏感度不同。可以尝试调整攻击强度参数。
- 框架差异:当前代码基于PyTorch和
art库重构,可能与原始TensorFlow版本的参数设置有所不同。

建议:如果遇到攻击无效的情况,请记录下使用的具体参数、攻击算法和模型,并在项目仓库中提交Issue,附上可复现的代码片段。
任务范围与数据量
是否需要为整个10000张测试集生成对抗样本?

建议:不需要对整个测试集进行操作。可以选择一个具有代表性的子集(例如几百到一千张图像)进行实验。这可以显著减少计算时间,尤其是对于运行缓慢的攻击算法(如DeepFool)。在报告中说明你选择的子集大小和理由即可。
重要:如果你基于自定义的子集生成对抗样本,请务必保存并提交对应的真实标签文件,以便评估时使用。
结果报告与提交
最后,我们来明确项目结果的提交格式。

项目报告需要展示你的实验结果和分析。推荐使用 Jupyter Notebook 格式提交,因为它能很好地结合代码、可视化图表和文字描述。

报告内容应包括:
- 实验设置:简要说明使用的数据集、攻击方法、模型和评估指标。
- 结果图表:绘制图表来展示不同攻击下,各种模型(无防御、ATHENA、PGD-ADT)的错误率对比。例如,可以绘制以攻击强度(如FGSM的epsilon)为横轴,错误率为纵轴的曲线。
- 结果分析:对图表进行解释。说明哪种攻击最有效,哪种防御最鲁棒,并尝试分析原因。
- 代码片段:包含生成关键图表的数据处理和绘图代码。
在Notebook中,你可以使用Markdown单元格撰写文字描述,用Code单元格执行绘图代码。
总结与后续步骤
本节课中我们一起学习了ATHENA项目任务一的核心流程:生成对抗样本和评估模型鲁棒性。我们明确了从环境配置、代码理解到结果报告的完整路径。
关键要点总结:
- 工作流:良性样本 -> 攻击算法 -> 对抗样本 -> 模型预测 -> 性能评估。
- 核心代码:使用
craft_adversarial_examples.py生成样本,使用model.predict()进行预测,使用error_rate()函数计算指标。 - 解决问题:遇到问题时,详细记录并在GitHub仓库提交Issue。
- 协作与学习:鼓励跨专业团队合作,积极向助教和同学提问。

下一步,请根据本教程的指导,着手解决环境配置问题,运行示例代码,并开始着手完成任务一的实验部分。祝你项目顺利!
7.2:卷积神经网络 🧠

在本节课中,我们将学习卷积神经网络(CNNs)的基本架构、核心操作及其与全连接网络的区别。我们将深入探讨卷积、池化等操作的工作原理,以及它们如何赋予网络平移不变性等关键特性。
网络架构概览
上一节我们介绍了卷积神经网络的基本概念。本节中,我们来看看其具体的架构设计。
卷积神经网络是一种分层架构。对于“层”的定义存在不同观点。一种观点认为,一个卷积层包含多个阶段。
- 卷积阶段:执行我们讨论过的卷积操作。
- 线性阶段:引入非线性激活函数(如ReLU)。
- 池化阶段:对数据进行下采样和汇总。
这三个阶段合起来通常被称为一个“卷积层”。另一种观点则将每个阶段单独视为一层。从表示的角度看,左边的定义更受青睐,因为每一层都产生一个张量输出。
与全连接网络的对比
为了理解CNN的独特之处,我们将其与熟悉的全连接网络进行对比。
在全连接网络中,每一层都是简单的矩阵乘法,将输入向量转换为输出向量。而在卷积神经网络中,每一层都将一个三维输入张量(具有高度、宽度和深度)转换为另一个三维输出张量。
网络末端,我们需要产生预测结果。例如,对于CIFAR-10数据集(10个类别),最终会输出一个大小为10的张量,这可以看作是每个类别的“得分”(logits)。之后可以通过Softmax函数将其转换为概率。
局部连接与参数共享
卷积神经网络的核心特性之一是局部连接。网络中的每个神经元并不连接到整个输入图像,而是只连接到输入的一个局部区域,这个区域称为该神经元的感受野。
另一个关键特性是参数共享。同一个卷积核(滤波器)会滑过整个输入图像的不同位置,这意味着在不同位置检测相同特征使用的是同一组参数。这极大地减少了需要学习的参数量。
考虑一个输入为32x32x3(CIFAR-10图像)的例子。如果我们使用一个5x5的卷积核,并且输入深度为3,那么我们需要 3个 这样的5x5核(每个通道一个)。因此,总参数量为 5 * 5 * 3 + 1 = 76(75个权重 + 1个偏置)。
重要的是,在宽度和高度维度上,我们通过滑动卷积核实现局部连接;而在深度维度上,连接是“全连接”的,即输出单元的每个位置都综合了输入所有通道的信息。
池化操作
以下是关于池化操作的介绍。
池化(如最大池化)的主要作用是下采样和提供平移不变性。它通过汇总一个局部区域(如2x2窗口)内的激活值来减少数据维度。




- 最大池化:取感受野内所有神经元的最大激活值。
- 平均池化:取感受野内所有神经元的平均激活值。
最大池化更常用,因为它能提供一定程度的平移不变性。平移不变性意味着当输入发生微小平移时,输出不会发生剧烈变化。例如,图像中的猫无论出现在左侧还是右侧,网络都应能识别出它。
池化操作也可以跨通道进行。假设一个层有三个通道,每个通道学习检测不同方向的特征(如左边缘、右边缘)。跨通道最大池化会从这三个通道中取最大值作为输出,这样无论哪个特征被激活,池化层都会产生较强的响应,从而获得对某些变换(如旋转)的不变性。
步长与下采样
步长是控制下采样程度的一个重要超参数。
步长 定义了卷积核或池化窗口在输入上每次移动的距离。步长为1时,窗口每次移动一个像素,输出尺寸变化较小。步长为2时,窗口每次移动两个像素,输出尺寸会减半,实现了下采样。
下采样的效果是保留最显著的特征(如较大的激活值),同时丢弃可能不重要的细节信息,这有助于减少计算量并增强特征的空间层次性。


典型CNN架构流程
现在,让我们将这些组件组合起来,看看一个典型的CNN架构是如何工作的。
一个典型的卷积神经网络遵循“由大到小”的流程:
- 输入:大型图像(如256x256x3)。
- 特征提取:图像经过多个“卷积-激活-池化”模块。卷积层学习特征,激活函数引入非线性,池化层进行下采样并增加不变性。经过多个这样的模块后,空间尺寸(高和宽)逐渐减小,而深度(通道数)可能增加。
- 展平与分类:最终得到一个较小的三维特征张量(例如7x7x512)。将其展平为一维向量(例如
7*7*512=25088维)。 - 全连接层:在展平后的向量上添加一个或多个全连接层,进行最终的分类决策,输出每个类别的得分。
- 输出:通过Softmax函数将得分转换为类别概率。
这种设计使得网络能够高效地从原始像素中提取层次化特征,并在最后阶段利用这些特征进行分类。
其他概念与变体
除了标准操作,还有一些其他概念和架构变体值得了解。
- 零填充:在输入张量的边界填充零。这有助于在卷积后保持输出尺寸不变,对于构建更深的网络非常有用。
- 局部连接层:类似卷积,神经元仅局部连接,但参数不共享。每个位置的连接权重都是独立学习的,参数量更大,灵活性也更高。
- 平铺卷积:介于标准卷积(参数完全共享)和局部连接(参数无共享)之间。参数在局部区域内共享,但在更大的“平铺”块之间不共享。这可以控制输出相邻单元之间的相关性。
- 跨通道部分连接:并非每个输出通道都连接到所有输入通道。这种稀疏连接可以大幅减少计算量和参数量,在如MobileNet等高效模型中常见。

总结
本节课中我们一起学习了卷积神经网络的核心内容。我们了解了CNN的分层架构,以及卷积、池化等关键操作如何通过局部连接和参数共享机制,高效地从图像中提取特征。我们探讨了池化带来的下采样和平移不变性好处,并梳理了从输入到分类输出的典型网络流程。最后,我们还简要介绍了零填充、局部连接等高级概念与变体。与全连接网络相比,CNN能以更少的参数和计算量构建更深的网络,并天然具备对平移等变换的鲁棒性,这使其成为计算机视觉任务中最主流的架构。
7.3:ATHENA框架任务一完整演示教程 🧪

在本教程中,我们将学习如何使用ATHENA框架完成第一个任务。我们将涵盖从准备数据、生成对抗样本到评估防御模型效果的完整流程。整个过程将通过Jupyter Notebook进行演示。

环境准备与数据导入
首先,确保你已经激活了正确的Python环境。在命令行中输入以下命令以启动Jupyter Notebook:

jupyter notebook
启动后,在浏览器中打开Notebook。本教程将使用为任务一准备的Notebook文件。
以下是开始前需要导入的必要包:
import numpy as np
import torch
import matplotlib.pyplot as plt
# ... 其他必要的导入
数据子采样(可选步骤)
考虑到某些攻击方法可能耗时较长,你可以选择生成一个较小的数据集用于实验。这是一个可选步骤。

我提供了一个名为 subsampling 的API来帮助你完成这个操作。该API可以从原始数据中按类别均匀地采样,并保存采样后的数据和对应的标签。

以下是该API的核心调用方式:

# 假设 be_sample 是原始数据,labels 是对应的标签
subsampling(be_sample, labels, num_classes=10, ratio=0.1, output_path='results', file_name='subsample')

参数说明:
be_sample: 原始数据样本。labels: 对应的真实标签。num_classes: 类别数量。对于MNIST数据集,这个值始终是10。ratio: 采样比例,默认为10%。output_path: 输出文件的保存路径。file_name: 输出文件的前缀。
运行成功后,你会在指定的 output_path 下看到生成的文件,文件名会带有 subsample 前缀和采样比例。
重要提示: Git通常不允许上传空文件夹。因此,你需要手动创建 results 文件夹(或你指定的其他输出文件夹),否则代码可能会因路径不存在而报错。
配置对抗样本生成
生成对抗样本前,我们需要加载实验配置。这些配置通常保存在一个JSON文件中(例如 data_analyst.json)。
如果你使用了子采样数据,需要将生成的子样本文件路径更新到这个JSON配置文件中。具体做法是,将子样本文件复制到配置指定的位置,并替换JSON文件中对应的文件名和标签路径。

更新后的配置将用于加载目标模型和待攻击的良性样本。



执行对抗攻击
现在,我们可以开始生成对抗样本了。核心是调用 generate_adversarial_examples 这个API。
# 伪代码示例
from attacks import generate_adversarial_examples
# 加载目标模型、数据和攻击配置
target_model = load_model(...)
benign_data, true_labels = load_data(...)
attack_configs = load_attack_configs(...) # 从JSON文件加载
# 生成对抗样本
adversarial_examples, success_rate = generate_adversarial_examples(
model=target_model,
data=benign_data,
labels=true_labels,
attacks=attack_configs,
save_results=True,
save_path='path/to/save/AEs'
)
攻击参数调优: 不同的攻击方法(如FGSM、C&W)有可调参数。你可以在对应的攻击函数文件(例如 attacks/fgsm.py)中查看和修改这些参数。在 attack_configs 中指定你想要的攻击方法和参数。
生成过程中,代码会遍历配置中的所有攻击方法,对每个良性样本生成对应的对抗样本,并使用目标模型进行预测,计算攻击成功率(即错误率)。

你还可以选择可视化生成的对抗样本以进行调试:
# 示例:绘制前5个对抗样本
for i in range(5):
plt.subplot(1, 5, i+1)
plt.imshow(adversarial_examples[i].squeeze(), cmap='gray')
plt.title(f'True: {true_labels[i]}, Pred: {preds[i]}')
plt.axis('off')
plt.show()

保存与记录对抗样本
生成对抗样本后,你需要记录它们以便后续评估。有两种方式:
- 更新主JSON文件:在你使用的
data_analyst.json文件中,添加一个新的键值对来存储你的对抗样本路径。{ ... // 其他配置 "task1_AEs": { "data_path": "path/to/your/generated/AEs.npy", "label_path": "path/to/the/corresponding/labels.npy" } } - 创建新的JSON文件:创建一个全新的配置文件来管理你的对抗样本,这样不会影响原始配置结构。
无论选择哪种方式,确保在后续评估代码中能正确加载到这些对抗样本。
评估对抗样本
最后一步是评估生成的对抗样本在不同防御模型下的效果。我们将评估三个模型:
- 无防御的原始模型(
undefended model)。 - 由多个弱防御模型组成的集成模型(
ATHENA ensemble)。 - 经过PGD对抗训练的基础模型(
PGD adversarially trained model)。
评估的核心是计算对抗样本的“错误率”。根据定义,我们需要先排除那些原始模型本身就已经分类错误的良性样本,因为对抗样本对它们的“欺骗”不是由扰动引起的。
以下是评估流程的关键代码:
from evaluation import error_rate
from ensemble import create_ensemble
# 1. 加载模型
baseline_model = load_pgd_trained_model(...)
undefended_model = load_target_model(...) # 与生成AE时是同一个模型
# 2. 创建集成防御
# 首先加载多个弱防御模型
weak_defenses = [load_wd1(...), load_wd2(...), load_wd3(...)]
# 创建集成,例如使用概率平均策略
athena_ensemble = create_ensemble(classifiers=weak_defenses, strategy='averaging_probability')
# 3. 加载良性样本和对抗样本
benign_data, true_labels = load_benign_data(...)
adv_examples = load_adv_examples(...)
# 4. 找出被原始模型正确分类的样本索引
correct_indices = get_correctly_classified_indices(undefended_model, benign_data, true_labels)
# 5. 评估对抗样本在原始模型上的错误率
undefended_preds = undefended_model.predict(adv_examples)
error_undefended = error_rate(undefended_preds, true_labels, correct_indices)
# 6. 评估对抗样本在集成模型上的错误率
ensemble_preds = athena_ensemble.predict(adv_examples)
error_ensemble = error_rate(ensemble_preds, true_labels, correct_indices)
# 7. 评估对抗样本在PGD训练模型上的错误率
baseline_preds = baseline_model.predict(adv_examples)
error_baseline = error_rate(baseline_preds, true_labels, correct_indices)
print(f"原始模型错误率: {error_undefended:.2%}")
print(f"集成模型错误率: {error_ensemble:.2%}")
print(f"PGD训练模型错误率: {error_baseline:.2%}")

结果解读:
假设你得到类似 原始模型: 88%, 集成模型: 67%, PGD模型: 21% 的结果。这表明:
- 对抗样本对原始模型非常有效(88%的成功率)。
- 由三个弱防御构成的集成模型提供了一定的防御能力,将错误率从88%降低到67%。
- PGD对抗训练模型提供了更强的防御,将错误率大幅降低到21%。

这体现了防御强度、模型复杂度和计算开销之间的权衡。ATHENA框架的灵活性在于,你可以自由选择集成中弱防御的数量、类型和集成策略,从而探索这个权衡空间。

实验设计与探索建议

你的任务不仅仅是运行上述流程。ATHENA框架提供了巨大的探索空间:
- 集成规模:尝试使用不同数量的弱防御模型(例如5个、10个、20个)构建集成,观察防御效果和开销的变化。
- 弱防御类型:框架提供了超过70种变换对应的弱防御。尝试组合不同类型的弱防御,观察哪种组合最有效。
- 集成策略:除了概率平均(
averaging_probability),还可以尝试多数投票(majority_voting)等策略。 - 自定义探索:你甚至可以探索框架之外的新变换,并训练对应的弱防御模型。
建议与你的团队成员一起进行头脑风暴,设计一个有趣的实验方案,旨在发现集成防御中不同因素(如规模、多样性、策略)如何影响其鲁棒性和效率。你的报告应该着重阐述这些设计选择背后的思考以及有趣的发现。
总结
本节课中,我们一起学习了使用ATHENA框架完成第一个任务的完整流程:
- 环境与数据准备:启动环境,可选地进行数据子采样。
- 对抗样本生成:配置攻击参数,针对目标模型生成对抗样本。
- 对抗样本评估:评估这些对抗样本在原始模型、自定义集成模型和基准防御模型上的效果。

关键在于理解,ATHENA是一个灵活的框架,你的主要工作是设计并执行实验,探索如何通过组合简单的弱防御来构建有效的集成防御系统,并分析其性能与成本的权衡。请充分利用这个框架进行探索和学习。
8.1:机器学习硬件 🖥️

在本节课中,我们将要学习机器学习系统的硬件层。我们将探讨不同硬件平台的特点,理解训练与推理在硬件层面的核心差异,并分析常见神经网络模型(如全连接网络、卷积网络)的计算特性与效率瓶颈。
概述
在之前的课程中,我们讨论了机器学习系统的软件栈,包括编程API和计算图系统层。本节课,我们将目光转向底层硬件。理解硬件对于构建高效的机器学习系统至关重要。我们将重点关注硬件面临的关键问题,以及当前该领域令人兴奋的研究进展。
硬件平台的多样性
如今,我们有多种多样的异构硬件平台。这些平台在宏观架构、内存、计算能力和功耗等方面存在差异。
以下是几种典型的硬件示例:
- 小型嵌入式硬件:例如树莓派等,体积小,功耗低。
- 专用加速器:例如谷歌的TPU,专为机器学习计算设计。
- 图形处理器:例如NVIDIA的GPU,广泛用于训练和推理。
- 车载计算平台:例如NVIDIA的Drive AGX Xavier,用于自动驾驶汽车。
尽管硬件形态各异,但我们需要理解其背后的通用原理和关键挑战。
核心挑战:性能与能效
机器学习硬件的主要目标可以归结为两点:运行得更快和运行得更高效。
运行得更快意味着提高吞吐量,例如每秒能完成更多的推理次数。
运行得更高效则主要关注能效,这对于部署在电池供电设备上的推理任务尤为重要。能效通常用 推理次数/焦耳 来衡量。我们也可以从成本角度考虑,即 每秒推理次数/美元。
训练与推理的差异
理解训练和推理在计算上的区别对硬件设计至关重要。
推理 只涉及神经网络的前向传播路径。
训练 则包含三个核心操作的循环:
- 前向传播:计算网络输出和损失。
- 反向传播:计算损失相对于各权重的梯度。
- 参数更新:根据梯度更新网络权重。
用伪代码可以表示为:
for epoch in range(num_epochs):
for batch in data_loader:
# 1. 前向传播
output = model(batch.input)
loss = loss_function(output, batch.label)
# 2. 反向传播
loss.backward()
# 3. 参数更新
optimizer.step()
optimizer.zero_grad()
全连接网络的计算特性
上一节我们介绍了训练与推理的基本流程,本节我们来看看不同神经网络模型的计算特性。首先分析全连接网络。
一个全连接层的基本操作是矩阵-向量乘法:输出激活 = 权重矩阵 × 输入激活。
如果输入层有 X 个单元,输出层有 Y 个单元,那么权重矩阵的大小为 X × Y。当 X 和 Y 都很大时(例如各4000),权重数量将达到1600万,这使得矩阵乘法成为非常昂贵的操作。
更关键的是,这些权重通常存储在RAM中。从RAM中读取大量数据是耗能的操作。并且,在进入下一层时,需要从RAM中加载一组完全不同的新权重,这导致了低效的重复内存访问。
批处理的优势:在训练时,我们可以将多个输入样本组成一个批次。这样,权重只需从内存中加载一次,就可以用于计算整个批次,从而显著提高了内存访问效率。然而在推理时,输入通常是逐个到达的,无法进行批处理。
激活与权重的稀疏性
在全连接网络中,激活值和权重矩阵通常具有稀疏性,这为提高计算效率提供了机会。
激活值的稀疏性:源于ReLU等激活函数。ReLU函数定义为 f(x) = max(0, x),它会使所有负值激活变为零。因此,激活向量中往往包含大量零元素。
权重的稀疏性:在训练后,许多权重值会接近于零。通过模型剪枝技术,我们可以将这些小权重设为零,从而获得一个稀疏的权重矩阵。
于是,核心的矩阵乘法操作可以变为 稀疏矩阵 × 稀疏向量 的乘法,这比稠密矩阵乘法要高效得多。但需要注意的是,对于不同的输入图像,激活向量中零的位置会发生变化,而权重矩阵中零的位置在剪枝后是固定的。
卷积网络的计算特性
接下来,我们分析卷积神经网络的计算特性。CNN通过卷积核在输入特征图上滑动来提取特征。
与全连接层相比,CNN的核心优势在于参数共享。一个卷积核的参数量远小于全连接层的权重矩阵,这减少了对内存带宽的压力。
然而,CNN需要进行大量的算术运算。为了计算输出特征图上的一个点,卷积核需要在输入的一片区域上进行多次乘加运算。其计算量可以用以下嵌套循环表示:
for 输出通道 j in range(J):
for 输入通道 k in range(K):
for 输出位置 (x, y) in output_map:
for 卷积核位置 (u, v) in kernel:
output[x, y, j] += input[x+u, y+v, k] * weight[u, v, k, j]
尽管单次乘加运算开销很小,但巨大的循环次数使得总体计算量非常可观。
此外,CNN中还有池化、ReLU等操作,这些操作计算开销相对较低。
训练中的内存限制
在训练深度CNN时,我们还会面临内存限制。网络权重、中间激活值、梯度等都需要存储在内存中。
当使用较大批量进行训练时,需要存储的中间数据量会急剧增加。因此,训练时的批量大小往往受限于GPU或加速器的可用内存容量。这是一个在实际系统设计中必须权衡的关键因素。
端到端系统视角
最后,我们需要从端到端系统的视角来看待效率问题。一个完整的机器学习流水线包括多个阶段:
- 从磁盘加载(可能压缩的)数据。
- 数据解码与预处理。
- 模型前向/反向传播计算。
- 结果输出。
如果只优化模型计算部分,而数据加载或预处理成为瓶颈,那么整体性能仍无法提升。因此,构建高效的机器学习系统需要全局优化整个流水线。
总结

本节课我们一起学习了机器学习硬件的基础知识。我们探讨了异构硬件平台,明确了追求更高性能和更高能效的核心目标,并区分了训练与推理的不同计算需求。我们深入分析了全连接网络和卷积网络的计算与内存访问模式,理解了稀疏性带来的优化机会以及训练中的内存限制。最后,我们认识到必须从端到端系统的角度进行优化,才能构建真正高效的机器学习系统。
8.2:机器学习硬件系统 🖥️

在本节课中,我们将继续讨论硬件层,这对于理解你编写的程序如何在底层硬件上运行至关重要。了解软件与硬件之间的连接,将极大地帮助你优化机器学习系统。
上一节我们介绍了性能对机器学习系统的重要性。本节中,我们将更详细地探讨在深度神经网络中实现并行化的机会,特别是在全连接网络和卷积神经网络中。
摩尔定律的现状与性能瓶颈 📉
你可能听说过摩尔定律,它描述了晶体管数量随时间增长的趋势。然而,更重要的指标是单线程性能,这直接关系到机器学习的计算效率。
近年来,单线程性能的增长已经趋于平缓,这被称为摩尔定律的终结。对于需要高计算量或高内存访问的机器学习系统而言,理解这一点非常重要。
神经网络中的并行化机会 🔄
在神经网络中,存在多种并行化机会,可以显著提升训练和推理效率。
数据并行
数据并行是一种直观的方法。以下是其基本步骤:
- 将训练数据分割成多个批次。
- 在不同的处理器上并行处理每个批次。
- 计算每个批次相对于当前权重的损失梯度。
- 协调来自所有工作节点的参数更新。
参数更新有两种主要范式:
- 同步更新:等待所有工作节点完成计算,汇总梯度后统一更新参数,再将新参数分发下去。缺点是速度受最慢节点限制。
- 异步更新:工作节点使用本地计算出的梯度立即更新一个本地参数副本,并继续计算,同时将梯度发送给参数服务器。参数服务器异步整合更新。这能避免等待,但可能使用过时的参数,需要调整学习率等技巧来保证收敛。
模型并行
对于卷积神经网络,可以利用其结构特点进行模型并行。
在输出特征图上并行:卷积核在输入上滑动生成输出特征图。可以将输出特征图划分为不同区域,并分配给不同的处理器并行计算。这得益于卷积核的参数共享特性。
在滤波器上并行:每个输出特征图由不同的滤波器生成。可以分配不同的处理器来并行计算不同的特征图。
对于全连接神经网络,也可以进行模型并行。可以将巨大的权重矩阵分割,并行计算不同的输出激活值。公式表示为将权重矩阵 W 分割为 W1 和 W2,分别与输入向量 a 相乘,得到输出激活 b1 和 b2。
层间依赖与挑战
神经网络层之间存在数据依赖,每一层的计算都依赖于前一层的输出。这种依赖性是性能优化的主要挑战之一,因为它限制了纯粹的层间并行。
超参数优化与神经架构搜索 🔍
除了模型参数,超参数(如学习率)和神经网络架构本身也对性能有巨大影响。
- 超参数优化:在巨大的超参数空间中进行搜索,以找到最佳组合。
- 神经架构搜索:自动寻找针对特定问题的最佳网络架构(如层数、每层神经元数量)。这是一个热门的研究领域,当同时考虑多个目标(如准确率和能耗)时,问题会变得更加复杂。
扩展性与性能收益 📈
增加计算核心通常能带来近乎线性的计算性能提升。研究表明,对于卷积神经网络等模型,结合数据和模型并行可以带来显著的训练加速。然而,收益取决于模型类型,例如某些循环神经网络模型可能无法从模型并行中获得同等收益,甚至可能因通信开销等原因导致性能下降。

内存访问的优化 🚀
在大型分布式训练中,内存访问可能成为瓶颈。权重参数需要从内存中频繁读取和更新。
传统上,GPU访问外部存储需要通过CPU,这造成了瓶颈。新技术如 GPU Direct Storage 允许GPU直接与存储设备通信,无需经过CPU,从而大幅提升了I/O效率。

高效的通信拓扑结构(如环状或网状)也能促进工作节点间梯度等信息的快速交换,加快收敛速度。
总结 📝


本节课我们一起学习了机器学习硬件系统中的关键优化思想。
我们探讨了摩尔定律的现状及其对性能的影响。
我们深入分析了数据并行和模型并行的原理与实现方式。
我们了解了超参数优化和神经架构搜索这两个重要的研究方向。
我们认识到内存访问可能成为系统瓶颈,并介绍了一些先进的硬件通信技术来缓解这个问题。
理解从高层代码到底层硬件的整个栈,能够为你提供优化机器学习系统的深刻洞察力。
8.3:机器学习硬件

在本节课中,我们将继续讨论硬件层,重点探讨降低精度在机器学习系统中的应用。我们将了解如何通过使用更低精度的数值表示(如整数或半精度浮点数)来存储权重和激活值,从而节省存储空间和能耗,同时确保模型精度不受显著影响。
概述
上一节我们介绍了硬件加速的基本概念,本节中我们来看看数值精度对机器学习系统性能与能耗的影响。使用低精度表示是模型压缩与量化的基础,能在保证准确性的前提下,显著提升能效和存储效率。
为何精度很重要
在计算机中,数值并非以数学上的绝对精度存储。不同的数值表示方法(如浮点数、整数)提供了不同的精度范围和存储成本。
- 浮点数:例如32位浮点数(FP32),使用部分比特表示指数、尾数和符号,能表示约70个数量级的数值范围,但精度会随数值大小变化。
- 整数:例如32位整数(INT32),表示的是固定间隔的离散值,在整个表示范围内具有一致的绝对精度,但能表示的数值范围远小于浮点数。
编程语言(如Python、C++)提供了不同精度数值类型的API。核心在于:更低的精度意味着更小的表示范围和/或更低的数值分辨率。
不同操作的能耗成本
执行神经网络计算主要涉及三种操作:内存读取、乘法和加法。它们的相对能耗成本差异巨大:
以下是不同精度下各类操作的近似能耗(单位:皮焦耳):
- 32位浮点乘法:3.7 pJ
- 16位浮点乘法:1.1 pJ
- 8位整数乘法:0.2 pJ
- 32位浮点加法:0.9 pJ
- 16位浮点加法:0.4 pJ
- 32位整数加法:0.1 pJ
- 8位整数加法:0.03 pJ
- 片上SRAM读取:5 pJ
- 片外DRAM读取:640 pJ
关键观察:
- 乘法比加法耗能多得多。
- 降低精度能大幅减少计算能耗(例如,从FP32乘法转到INT8乘法,能耗降低超过15倍)。
- 内存访问(尤其是片外DRAM访问)是能耗最高的操作,比计算操作高出数个数量级。因此,减少数据(特别是权重)从片外内存的读取至关重要。
芯片设计还需考虑面积成本,更高精度的计算单元通常需要更大的芯片面积。
混合精度推理策略
考虑到不同操作的能耗以及权重与激活值数量的巨大差异,混合精度策略在实践中非常有效。
在一个全连接层的前向传播(推理)中,核心操作是:
输出激活值 = ∑ (权重 * 输入激活值)
一个高效的混合精度方案可能是:
- 权重存储为4位整数:因为权重数量极多(例如数百万个),将其以最低精度存储在片外DRAM中,能极大减少内存带宽占用和读取能耗。
- 激活值存储为16位浮点:激活值数量相对较少,可以使用较高精度。
- 计算时:将4位权重重整(解码)为16位,与16位激活值进行16位乘法。
- 累加时:使用24位或32位精度累加多个乘积结果,以避免在大量加法中丢失精度。最终输出可能再量化为16位用于下一层。
这种设计合理的原因:
- 权重:数量庞大,是内存访问的瓶颈,故采用最低精度存储。
- 激活值:数量相对少,对精度更敏感,且是层间传递的数据,故采用较高精度。
- 累加器:加法能耗相对较低,使用更高精度可防止累积误差,且不影响整体能耗瓶颈(瓶颈在于乘法)。
训练中的低精度挑战与技巧
在训练的反向传播阶段,需要更新权重:权重更新量 = 学习率 * 梯度。
挑战:训练后期,梯度值和学习率都会变得非常小。在低精度表示下,两个极小值的乘积可能被舍入为零,导致权重无法更新,训练停滞。
解决方案:随机舍入
- 常规舍入:将数值舍入到最接近的可表示值。
- 随机舍入:以一个概率将数值向上或向下舍入。例如,一个值更接近0,则以高概率舍入到0,低概率舍入到1。这确保了权重更新的期望值是无偏的,能够收敛到正确的值。
P(舍入到1) = 值 / 步长,P(舍入到0) = 1 - P(舍入到1)
批归一化的作用:它对一个批次内的激活值进行归一化,使其分布集中在相近的范围内。这避免了因不同样本激活值动态范围差异过大,在低精度下被差异化处理而引入偏差,有利于稳定低精度训练。
精度与性能的权衡
降低精度需要在模型准确性和能效/存储收益之间进行权衡。
- 将权重从FP32降至INT16通常能带来显著的能耗降低,且精度损失可忽略。
- 降至INT8可能带来更大的能效提升,但需要仔细评估精度损失是否在可接受范围内。
- 通过使用混合精度训练(如FP16/FP32混合)和随机舍入等技术,可以在低精度下实现与全精度训练相似的收敛性和最终精度。
总结

本节课中我们一起学习了硬件中数值精度的关键作用。我们了解到:
- 降低精度(量化)是节省存储空间和计算能耗的有效手段。
- 内存访问,尤其是片外访问,是系统能耗的主要来源。
- 混合精度策略能根据数据特性和操作类型优化能效。
- 在训练中应用低精度需要克服梯度消失等挑战,随机舍入和批归一化是重要的辅助技术。
这些概念是模型压缩与量化的基础,对于在资源受限的边缘设备上部署高效的机器学习系统至关重要。
9.1:模型压缩(剪枝与量化)📉

在本节课中,我们将要学习模型压缩技术,这是机器学习系统栈中硬件层相关内容的延续。核心目标是探讨如何利用数学原理或现代硬件架构的洞察,来压缩深度神经网络,从而减少其存储空间和计算量,使其更高效。
上一节我们讨论了硬件层的基础,本节中我们来看看如何通过剪枝和量化这两种主要方法来优化模型。
概述
模型压缩旨在减少神经网络的大小和计算量。大型模型拥有大量权重参数,无论是全连接层还是卷积层,它们都涉及大量计算。通过压缩,我们可以在保持模型性能的同时,显著降低其存储需求和推理时的计算开销,这对于降低能耗、提升推理速度以及适应移动设备等资源受限环境至关重要。
剪枝 (Pruning) ✂️
剪枝的基本思想是移除神经网络中不重要的连接或神经元。你可以将神经网络想象成一棵树,剪枝就是剪掉一些树枝。具体来说,可以剪枝权重(突触),也可以剪枝神经元。
其背后的直觉是,许多权重值非常接近于零,这些“微弱”的连接对激活值的影响很小。因此,保留它们需要付出存储和计算成本,但收益甚微。剪枝掉这些连接可以大幅减少模型参数。
以下是剪枝方法通常遵循的三步流程:
- 训练网络:首先,正常训练一个完整的神经网络。
- 剪枝连接:根据设定的阈值(例如,将绝对值小于某个值的权重置零),剪枝掉不重要的连接。
- 重新训练:在固定已剪枝权重为零的前提下,重新训练网络,以恢复因剪枝可能损失的精度。
如果一次性剪枝过多权重,精度会显著下降。通过重新训练,甚至可以进行迭代式剪枝(即多次“剪枝-重训练”循环),从而在剪枝掉极高比例(如90%)权重的情况下,仍能保持与原模型相近的精度。
以下是一些经典论文中的实验结果:
- 在AlexNet上,全连接层可剪枝约90%的权重,卷积层可剪枝约三分之二的权重。
- 在VGG网络上,总体可移除约93%的权重。
- 对于LSTM等循环神经网络,同样可以在剪枝90%权重后通过重训练保持精度。
剪枝不仅能减小模型尺寸,还能加速推理。实验表明,在CPU上,剪枝后的模型可获得约3倍加速;若部署在GPU上,结合剪枝可获得高达48倍于原始CPU版本的加速。
量化 (Quantization) 🔢
量化关注的是权重的数值表示形式。其核心思想是使用更少的比特数(例如,用8位整数代替32位浮点数)来表示权重和激活值,从而减少存储空间和计算开销。
标准的量化方法是均匀量化,即将数值范围均匀分割。例如,用4比特(可表示16个值)均匀地表示一个区间内的数字。
然而,更高效的方法是训练后量化。观察剪枝后权重的分布,通常会呈现双峰或多峰形态,权重值集中在几个特定的区间。如果使用均匀量化,许多量化级别会浪费在权重分布稀疏或没有权重的区域。
训练后量化的关键思想是进行非均匀量化,将有限的比特数(即有限的量化级别)更集中地分配到权重实际分布的区间。这通常通过聚类算法(如K-Means)来实现:
- 给定权重张量和目标比特数
b(对应2^b个聚类中心)。 - 对这些权重运行K-Means聚类,得到
2^b个聚类中心。 - 每个权重值用其所属聚类中心的索引(即量化后的值)表示,存储时只需保存
b比特的索引和2^b个聚类中心值(码本)。 - 可以进一步对量化后的模型进行微调,以提升精度。
实验表明,对于卷积层,通常需要多于4比特的表示来维持较好精度;而对于全连接层,使用2-4比特量化通常能在精度损失很小的情况下大幅压缩模型。
结合剪枝与量化 ⚡
单独使用剪枝或量化都能有效压缩模型,但将两者结合能产生更好的效果。
典型的流程是:先对模型进行迭代式剪枝和重训练,在保持精度的前提下大幅减少参数数量;然后对剪枝后的模型进行训练后量化,进一步降低每个参数的存储开销。
研究结果显示,通过结合剪枝与量化,可以将像VGG这样的大型模型(原始约500MB)压缩到仅约11MB(约原大小的2%),同时几乎不损失Top-1和Top-5分类精度。这种程度的压缩对于将模型部署到移动应用、嵌入式设备或存在存储、带宽限制的场景具有革命性意义。
总结
本节课我们一起学习了模型压缩的两大核心技术:剪枝和量化。
- 剪枝通过移除冗余的连接或神经元来减少参数数量。
- 量化通过降低权重数值的表示精度来减少存储和计算开销。
- 将两者结合可以最大程度地压缩模型,在极小精度损失下实现模型尺寸的急剧缩减。


模型压缩对于构建高效、低功耗、可部署在资源受限环境中的机器学习系统至关重要,是连接算法创新与实际应用落地的重要桥梁。
9.2:基于学习的策略教程 🧠

在本教程中,我们将学习如何为ATHEAN集成系统实现一个基于学习的策略。具体来说,我们将探讨如何收集弱防御模型的原始预测,并将其整理成适合训练一个回归模型的数据集,该模型将学习如何为每个输入样本选择最佳的弱防御模型。
概述
上一节我们介绍了任务的基本概念。本节中,我们将详细讲解如何利用API收集数据,并准备用于训练策略模型的数据集。核心在于理解原始预测数据的结构,并进行正确的维度转换。
数据收集与API使用
我们提供了一个API来收集集成系统中各个弱防御模型对输入数据的原始预测。这些预测以概率或逻辑值的形式返回。
以下是收集原始预测的核心代码示例:
# 假设我们已经加载了一个名为`athena`的集成模型和一个测试数据集`test_data`
raw_predictions = athena.predict(test_data, raw=True)
此函数返回一个三维数组,其形状为 (num_weak_defenses, num_samples, num_classes)。例如,如果集成使用了3个弱防御模型,在2个样本(图像)上进行10分类任务,那么数组形状将是 (3, 2, 10)。
数据格式转换
然而,对于训练策略模型,我们通常期望的数据格式是:对于每一个输入样本,都能看到所有弱防御模型对其的预测。这意味着我们需要交换数组的第一维和第二维。


以下是进行维度转换的方法:
# 将形状从 (弱防御数, 样本数, 类别数) 转换为 (样本数, 弱防御数, 类别数)
transposed_predictions = np.transpose(raw_predictions, (1, 0, 2))
转换后,transposed_predictions[0] 将包含第一个样本的所有弱防御模型的预测,这正是我们构建训练数据表所需的形式。
训练与测试数据分离的重要说明
一个关键的注意事项是:绝不能使用最终的测试集来训练你的策略模型。测试集在训练阶段必须是未知的,否则会导致模型过拟合,无法评估其真实性能。
以下是关于数据划分的建议:
- 如果你计划使用对抗样本(AEs)来训练策略模型,必须先将可用的对抗样本划分为训练集和验证集。
- 例如,如果有10K个对抗样本,你可以使用8K个作为训练数据,保留2K个用于后续测试模型性能。
- 在训练集上拟合你的回归模型,然后在未见过的验证集上评估它。
可选:收集逻辑值
除了概率,你可能还想获取模型在Softmax层之前的原始输出(即逻辑值)。这可以通过在加载模型池时设置参数来实现。
以下是收集逻辑值预测的示例:
# 在加载弱防御模型池时,设置 use_logits=True
# ... 加载模型池的代码 ...
raw_logits = athena.predict(test_data, raw=True)
# 后续的转置操作与概率值相同
逻辑值可能包含比概率更丰富的信息,有时对训练策略模型更有帮助。
总结

本节课中我们一起学习了如何为ATHEAN的基于学习策略任务准备数据。我们介绍了如何使用提供的API收集弱防御模型的原始预测,如何通过转置操作将数据转换成更合适的格式,以及强调了将训练数据与测试数据严格分离的重要性。这些步骤是构建一个能够学习如何为不同输入选择最佳防御模型的基础。
9.3:构建混合集成模型 🧠

在本节中,我们将学习如何构建一个混合集成模型。混合集成模型是指使用多种不同类型的基础分类器(例如,CNN和SVM)组合而成的集成模型。我们将介绍如何配置、加载不同模型,并探讨如何选择基础模型来构建有效的混合集成。

概述
在本任务中,我们将构建一个混合集成模型。与任务1不同,这里的基础模型将采用不同的架构,例如卷积神经网络(CNN)和支持向量机(SVM)。我们将学习如何配置这些模型,如何从候选池中选择它们,并最终评估混合集成模型的对抗鲁棒性。
模型配置

上一节我们介绍了单一类型模型的集成。本节中,我们来看看如何为不同类型的模型进行配置。
你可以为每种模型类型创建单独的配置文件。例如,一个用于CNN模型的配置文件 model_cnn.json 可能如下所示:
{
"architecture": "CNN",
"model_path": "./models/cnn/",
"parameters": {...}
}
同样,你需要为SVM模型创建另一个配置文件,例如 model_svm.json。
或者,你也可以将所有模型类型的配置信息整合在同一个配置文件中,就像本演示中所做的那样。这种方法将所有配置集中管理,便于查看和修改。
加载模型
以下是加载不同类型模型到各自候选池的步骤:
- 加载基准模型:这包括PGD对抗训练模型和未防御的基线模型。
- 加载候选弱防御模型:由于我们有两种模型类型,我们将分别加载到两个独立的池中。
- 使用CNN配置加载CNN模型到
cnn_pool。 - 使用SVM配置加载SVM模型到
svm_pool。
- 使用CNN配置加载CNN模型到
在本次演示中,我们使用了一个非常小的候选池作为示例,每个池只包含3个弱防御模型。
弱防御选择策略
你的主要工作是为混合集成设计一个选择弱防御模型的策略。这里提供一个非常简单的示例策略:
首先,固定用于构建集成模型的总弱防御数量,例如50个。
- 混合集成变体1:使用50个CNN模型和0个SVM模型。
- 混合集成变体2:用5个SVM模型替换5个CNN模型,得到45个CNN和5个SVM。
- 混合集成变体3:再替换5个,得到40个CNN和10个SVM。
- 依此类推,生成一系列混合集成变体。
这只是一个基础示例。我们期望你能提出更智能、更有效的选择策略,例如基于模型多样性、在干净数据或对抗样本上的性能等进行选择。
构建与评估集成模型
选定CNN和SVM的候选模型后,就可以构建混合集成模型了。以下是一个构建示例:
# 假设 selected_cnn 和 selected_svm 是选出的模型列表
weak_defenses = []
weak_defenses.extend(selected_cnn) # 添加选中的CNN模型
weak_defenses.extend(selected_svm) # 添加选中的SVM模型
# 使用“对抗概率”作为集成策略构建模型
ensemble_model = build_ensemble(weak_defenses, strategy='adversarial_probability')
接下来进行模型评估。评估前需要准备数据,包括加载对抗样本、真实标签,并确定哪些样本能被未防御模型正确分类,以便后续计算错误率。
评估过程在一个循环中进行(如果你有多个集成变体):
- 加载对抗样本。
- 使用未防御模型评估对抗样本的错误率。
- 使用我们构建的混合集成模型评估对抗样本的错误率。
- 使用基准模型(如PGD对抗训练模型)评估对抗样本的错误率。
在循环中,请注意保存每个集成变体结果时的键名(例如 ensemble_1, ensemble_2),以区分不同变体的性能。
关于SVM模型的说明
我们使用的SVM模型是基于 scikit-learn 库的。这些模型以 pickle 格式存储。为了在ATHENA框架中将其作为弱防御使用,我们需要一个包装器(Wrapper)来适配接口。你需要使用 pickle 包来加载这些模型,并使用提供的包装器对其进行封装。
结果示例
运行评估后,你可能会得到类似以下的结果:
- 对于使用FGSM攻击(ε=0.03)生成的对抗样本,未防御模型的错误率约为88%。
- 我们的混合集成模型能将错误率从88%降低到约40%。
- 而PGD对抗训练模型能将错误率从88%降低到21%。
这个示例展示了混合集成模型在提升对抗鲁棒性方面的潜力。

总结

本节课中我们一起学习了如何构建混合集成模型。关键步骤包括:为不同类型模型进行配置、分别加载模型到候选池、设计弱防御模型的选择策略、实际构建集成模型并进行评估。虽然示例中的选择策略较为简单,但希望你能够在此基础上,设计并实现更优的策略来提升集成模型的性能。
9.4:鲁棒因果迁移学习 👨🏫


在本节课中,我们将学习如何利用因果推断的思想,使机器学习模型在面对数据分布变化时,依然能保持鲁棒的预测性能。我们将从一个简单的例子出发,逐步理解核心概念,并最终介绍一种名为“鲁棒因果迁移学习”的方法。
概述
传统的机器学习模型通常在训练数据和测试数据来自相同分布的假设下工作。然而,在实际应用中,模型常常需要部署到与训练环境不同的新环境中,这被称为“域偏移”或“分布偏移”。本节课将探讨如何通过识别“因果不变特征”,使模型在源域和目标域之间实现鲁棒的迁移学习。
动机:为什么需要鲁棒性?
上一节我们概述了课程目标,本节中我们来看看一个具体的动机示例。
想象一个分类任务:区分奶牛和骆驼。我们有一个训练数据集,其中大部分骆驼的背景是沙漠,而奶牛是绿色的草地。一个传统的机器学习模型可能会很快学会将“沙漠背景”与“骆驼”关联,将“绿色背景”与“奶牛”关联。
然而,当这个模型被部署到一个新环境时,测试数据可能包含背景是草地的骆驼,或者背景是沙漠的奶牛。由于模型过度依赖背景(一个非因果的、会变化的特征),它很可能会在新环境中分类失败。
这个简单的故事可以推广到许多现实世界的问题。存在一些“上下文变量”,它们在训练环境和测试环境之间的分布会发生偏移。如果模型的学习依赖于这些变量,其性能就会下降。
核心概念:因果图与d-分离
为了形式化地解决这个问题,我们需要一些关于因果图和d-分离的背景知识。
一个因果图(或有向无环图,DAG)是一个没有有向环的图。图中的有向边具有因果解释,例如,边 X -> Y 表示 X 是 Y 的原因。
d-分离是判断图中两个变量集在给定第三个变量集时是否条件独立的一种标准。理解d-分离需要了解三种基本连接方式:
以下是三种基本连接方式及其d-分离性质:
-
链式连接(Serial Connection):
A -> B -> C- 如果观测了中间变量
B,则A和C被d-分离(即独立)。 - 如果未观测
B,则A和C通常是相关的。
- 如果观测了中间变量
-
分叉连接(Diverging Connection):
B <- A -> C- 如果观测了共同原因
A,则B和C被d-分离。 - 如果未观测
A,则B和C通常是相关的。
- 如果观测了共同原因
-
对撞连接(Colliding Connection):
B -> A <- C- 如果观测了对撞点
A,则B和C变得相关(即d-连接)。 - 如果未观测
A,则B和C被d-分离(即边际独立)。
- 如果观测了对撞点
理解这些连接方式后,我们可以分析之前的抽象例子。假设我们想预测变量 X2,并且存在一个上下文变量 C1,其分布在环境间会偏移。连接 C1 -> X1 -> X2 是一个链式连接,观测 X1 会阻断 C1 对 X2 的影响。而连接 C1 -> X3 <- X2 是一个对撞连接,观测 X3 会打开 C1 到 X2 的路径。因此,如果使用 X3 来预测 X2,上下文变量 C1 的分布偏移就会通过这条打开的路径直接影响预测,导致性能下降。这解释了为什么使用 X1 比使用 X3 更好。
实例:糖尿病诊断
让我们看一个更贴近现实的例子:糖尿病诊断。
我们收集了患者数据,包括年龄、伤口愈合延迟、局部麻痹、虚弱和视力模糊等症状。我们知道,所有这些症状都受年龄影响。同时,糖尿病会导致虚弱和视力模糊,而这两种症状又可能引起局部麻痹和愈合延迟。
假设真实的因果图如课程所示。我们按年龄(以50岁为界)将数据分为“年轻”和“年老”两个环境。可以观察到,几乎所有变量(包括糖尿病本身)的分布在两个环境间都存在显著偏移。
在这种情况下应该怎么做?根据因果图分析,我们不应该使用“虚弱”和“视力模糊”这两个变量来跨环境预测糖尿病。因为使用它们(即对其条件化)会打开“年龄”到“糖尿病”的路径(它们是对撞连接中的对撞点)。年龄分布在环境间的偏移会通过这条路径影响预测。
更好的选择是使用“局部麻痹”和“愈合延迟”。首先,它们是糖尿病的结果(因果效应)。其次,使用它们不会打开年龄到糖尿病的路径(它们是链式连接的中间变量),从而阻断了年龄分布偏移的直接干扰。实验结果表明,使用全部四个症状的均方误差为0.29,而仅使用这两个“因果不变特征”的均方误差降至0.2。
问题定义与假设
我们的核心任务是:给定数据集,找出那些能够使预测在源环境和目标环境之间保持鲁棒的因果不变特征。
然而,我们通常并不知道真实的因果模型。因此,我们希望开发一种方法,即使在不已知因果图的情况下也能找到这些特征。为此,我们需要一些假设:
以下是该方法依赖的核心假设:
- 马尔可夫性(Markov Condition):在因果图中,d-分离意味着数据中的条件独立性。
- 忠诚性(Faithfulness):数据中的条件独立性必然由因果图中的d-分离导致。(即图与数据没有意外的独立性)
- 无直接因果假设:上下文变量(C)与目标变量(T)之间没有直接的因果边。这是一个当前工作的局限性。
- 允许未观测混杂因子:我们不假设因果充分性。数据中可能存在未观测的共同原因(混杂因子),这通过使用有向无环混合图(ADMG) 来建模,该图允许双向边
<->表示未观测的混杂。
解决方案:利用马尔可夫毯
在ADMG中,我们证明了一个关键结论:要找到因果不变特征,只需关注目标变量 T 的马尔可夫毯。
马尔可夫毯是图中能使目标变量与图中所有其他变量条件独立的最小变量集合。在DAG中,它包含 T 的父节点、子节点以及子节点的其他父节点(共亲)。
在允许双向边的ADMG中,马尔可夫毯被推广为:所有与 T 之间存在对撞路径的变量集合。(单条边也被视为对撞路径)。
聚焦于马尔可夫毯有两个主要优势:
- 缩小搜索空间:无需在全变量集中搜索,提高了算法的可扩展性。
- 理论保证:我们证明了,要找到能d-分离上下文变量与目标变量的特征集,只需在马尔可夫毯的子集中寻找即可。
此外,另一个重要贡献是,我们证明了传统的马尔可夫毯学习算法(原本为DAG设计)可以直接应用于ADMG而无需修改,这非常实用。
RCTL算法流程
基于以上理论,我们提出了鲁棒因果迁移学习(RCTL) 算法。其流程非常简单直接:
以下是RCTL算法的核心步骤:
- 学习马尔可夫毯:从数据中学习目标变量
T的马尔可夫毯MB(T)。 - 搜索分离集:在
MB(T)中寻找一个最小的变量子集S,使得在给定S的条件下,上下文变量C与目标变量T独立(即C ⊥ T | S)。 - 输出与预测:如果找到这样的集合
S,则使用S中的特征在目标环境中进行预测;如果找不到,则算法报告失败。
实验评估
我们在合成数据集和真实数据集上评估了RCTL算法的性能。
在合成数据中,我们从一个已知的因果图生成数据,并在源和目标环境中对上下文变量施加分布偏移。结果表明,与其他迁移学习或特征选择方法相比,RCTL在预测均方误差上表现更优,且具有可扩展性。
在真实的糖尿病数据集上,我们按年龄划分环境,使用RCTL预测糖尿病。结果同样显示,RCTL能够取得更好的预测性能,这验证了该方法在未知真实因果模型时的实用性。
总结与未来方向
本节课中我们一起学习了如何将因果推断用于鲁棒迁移学习。
我们从一个经典的域适应问题出发,解释了为什么依赖非因果的关联特征会导致模型在分布偏移下失效。通过引入因果图、d-分离和马尔可夫毯的概念,我们形式化了“因果不变特征”的思想。接着,我们介绍了RCTL算法,它能够在未知因果图的情况下,高效地寻找这类特征,从而提升模型在新环境中的鲁棒性。


当前工作存在一些局限性,例如假设上下文变量与目标变量无直接因果边。未来的研究方向包括放松这些假设,以及探索如何将因果推理更广泛地应用于提升机器学习系统的稳健性和可解释性。

浙公网安备 33010602011771号