CMU 11-785 深度学习导论笔记(一)
1:课程导览与安排 📋
在本节课中,我们将学习卡内基梅隆大学《深度学习导论》课程(2024年秋季)的整体安排、课程目标、评估方式以及学习准备。我们将了解神经网络的基本概念、课程结构、作业与项目要求,以及如何有效地参与课程学习。
什么是神经网络?🧠
神经网络是人工智能中的一种方法,它教会计算机以受人类大脑启发的方式处理数据。近年来,它已成为各种模式识别、预测和分析问题的主要研究方向之一。神经网络在许多问题上确立了最先进的技术水平,并且常常大幅超越之前的基准。
上一节我们介绍了神经网络的基本定义,本节中我们来看看神经网络带来的一些突破性应用。
以下是神经网络取得突破的一些领域:
-
语音助手:例如 Siri、Alexa、Google Assistant。
-
视觉与感知:例如人脸检测、人脸识别、物体检测、语义分割。
-
自动驾驶汽车。
-
音乐生成。
-
图像生成(如DALL-E) 以及 ChatGPT。
谁适合学习本课程?🎯
本课程专为任何希望学习深度学习并愿意每周投入12至20小时(取决于已有知识基础)的人设计。我们希望学生愿意持续在Piazza论坛上提供反馈并进行互动,因为这是本课程的主要沟通方式。同时,我们希望学生乐于接受挑战,并为未来的AI研究做好准备。
为了学习本课程,你需要具备以下基础知识:
-
编程:需要掌握Python,本课程将使用PyTorch。至少应学过编程基础。
-
数学:需要了解微积分和线性代数。如果具备向量微积分和软件工程背景会更有帮助,但最重要的是掌握Python、微积分和线性代数。
课程目标与内容概览 🗺️
从宏观角度看,本课程旨在让你理解神经网络,并理解那些能够完成前述任务(如语音识别、图像生成等)的模型。我们希望你能无畏地为各种任务设计和训练这些网络。
更具体地看,本课程将涵盖以下概念:
-
神经网络发展的简要历史视角。
-
神经网络的类型及其核心思想。
-
学习是如何发生的。
-
神经网络的架构与应用。
在实践层面,我们希望你能熟悉训练过程,能够实现各种不同的架构,并了解某些问题的最先进解决方案。总体目标是为你未来的研究领域工作打下基础。
以下是本课程将涵盖的主题列表:
-
基础网络形式:包括多层感知机、卷积神经网络、循环神经网络、玻尔兹曼机。
-
高级形式:包括生成模型、对抗模型、图神经网络以及Transformer。
-
应用领域:计算机视觉(图像识别)、文本处理(语言建模与生成)、机器翻译(序列到序列建模)、分布建模与数据生成,以及语音识别。
课程网页上列出了参考书目,并且在每节课的页面中也会提供额外的阅读材料(文章)。
课程安排与后勤 📅
教师与助教
本课程的主讲教师是Bhiksha Raj教授,他将负责所有讲座。课程助教名单及其联系方式公布在课程网站上,他们来自匹兹堡和基加利校区,提供线上和线下的办公时间。
讲座与讨论
课程讲座将进行直播以供远程学生观看,同时也会被录制并上传到课程网站。观看讲座非常重要,因为测验内容与讲座相关,并且我们会记录出勤情况。
课程讨论将在Piazza论坛上进行,请务必关注最新动态。计算基础设施方面,我们将为所有学生提供Amazon代金券,并协助使用Google Cloud。
出勤与参与
出勤将计入成绩(占1%),因为我们注意到出勤的学生通常表现更好。我们主要通过课堂实时投票来追踪出勤。教授会在讲座中发起投票,你需要在Piazza或Zoom(如果教授远程授课)上在规定时间内(通常30秒)作答。答案对错不重要,重要的是参与。对于时区不便的学生,可以通过观看录制讲座来获得出勤分,但必须在讲座后一周内完整观看视频。
课程后半部分的日程可能会根据教授的行程和客座讲座安排略有调整。
作业、测验、项目与评分 📝
测验
每周会有一次测验,问题涉及当周讲座幻灯片中涵盖的主题。测验通常在周五晚上发布,周日晚上截止。本学期共有14次测验,我们将取其中最好的12次成绩计入最终评分。每次测验包含10道选择题,你有3次尝试机会以取得最佳成绩。
请注意:测验问题可能来自幻灯片上提及但教授未在课中讲到的内容,也可能来自教授在课中提到但未写在幻灯片上的内容。有时还会涉及需要阅读指定论文才能回答的问题。
作业
本学期有4次计分作业(作业1至4),以及一次不计分的预备练习(第0次辅导课)。每次作业分为两部分:
-
第一部分:自动评分的编程问题,使用自定义的神经网络工具包(如
my_torch)从头开始实现。这部分考察你独立编写神经网络代码的能力。提交时需要特别注意软件包版本,否则自动评分器可能给出零分。提前提交可获得加分。 -
第二部分:在真实数据集上解决复杂问题的开放性问题,以竞赛形式在Kaggle上进行评分。评分基于你在排行榜上的表现,采用线性插值法计算得分。有早期提交截止日期(占10分,旨在督促起步)和准时提交截止日期(占90分)。整个学期共有10天的延迟提交额度(总计,非每次作业)。
项目(仅限11-785和11-685课程)
项目旨在锻炼你理解和实现超越作业范围的新想法的能力。项目范围很广,你可以:
-
实现并评估最新论文中的前沿思想。
-
尝试解决一个问题,如果做得好,可能导向发表。
-
尝试全新的想法,例如新的学习算法、技术或模型。
项目以小组形式进行(11-785课程为3-4人,11-685课程为2人)。要求包括提交项目提案、中期报告、最终报告以及一个5分钟的视频演示。每个小组都会分配一名助教作为导师。视频发布后,你需要回答其他同学在Piazza上提出的问题。
评分构成
-
每周测验:占总成绩的 24%。
-
作业:占总成绩的 50%。
-
团队项目:占总成绩的 25%(11-485课程无此项,评分比例会相应调整)。
-
出勤:占总成绩的 1%。
学习准备、团队合作与指导 🤝
准备工作
本课程实践性很强,涉及大量编码和实验,需要处理大型数据集。主要使用Python和PyTorch。如果你觉得起点较低,强烈建议完成第0次辅导课的练习。
课程沟通
所有课程沟通都在Piazza上进行,包括课堂投票、问答、公告、团队协作以及教授的笔记。请务必下载该应用。
学习小组
每位学生都必须加入一个学习小组,并会分配一名助教导师。我们建议每组3-4人。在学习小组中,你们可以讨论作业、论文、课堂内容和测验。小组成员也可以同时是项目团队成员。
学术诚信
合作学习很重要,但必须遵守规则:
-
测验:必须独立完成。可以讨论问题,但答题时必须独自进行。
-
作业:必须独立解决。可以讨论代码和调试,但最终提交的每一行代码都必须是自己的成果。
-
严禁抄袭。你是来学习深度学习的,请挑战自己,不要降低标准。
寻求帮助
如果你的测验或作业成绩突然大幅下滑,你的助教导师可能会主动联系你。如果你遇到困难、感到落后或课业压力过大,请主动联系你的导师、任何助教或教师。课程还提供了关于如何应对困难的特定辅导视频。
课程挑战与成功秘诀 💪
本课程难度很高,需要每周投入大量时间。但如果你能跟上进度,它会充满乐趣。课程采用掌握性评估:
-
测验旨在检验你对讲座概念的理解。
-
作业教你实现和优化复杂的神经网络。
-
项目让你接触解决真实世界问题的过程。
任何在本课程中获得A的学生,从技术上讲都已准备好从事深度学习相关的工作。
再次强调,如果你对课程准备程度有疑虑,请务必完成第0次辅导课。作业1的第一部分包含了对后续课程有帮助的内容,争取拿到满分,它也是最容易的作业。
如有任何问题,请在Piazza上发帖,我们会尽力在5分钟内回复。
感谢你选修本课程,期待在讲座和办公时间见到你!
本节课总结:我们一起学习了《深度学习导论》课程的总体安排,包括神经网络简介、课程目标、适合人群、课程内容结构、详细的评分与考核方式(测验、作业、项目),以及成功完成课程所需的学习准备、团队合作规范和可用的支持资源。记住,积极参与、独立完成作业并善用沟通渠道是成功的关键。
2:深度学习导论与神经网络基础 🧠
在本节课中,我们将学习深度学习与神经网络的基本概念。我们将从历史背景出发,探讨连接主义思想,了解早期模型及其局限性,并最终介绍现代神经网络的基本原理和强大能力。
概述:为什么是神经网络?
近年来,神经网络已应用于几乎所有能想到的问题,并在许多领域达到了最先进的水平。从2016年左右开始,基于神经网络的AI无处不在,这正是我们学习它的原因。
从大脑到机器:连接主义思想
上一节我们看到了神经网络的广泛应用,本节中我们来看看其思想根源——连接主义。
人类大脑由约800亿个神经元和约100万亿个连接构成。大脑的处理能力完全取决于这些神经元之间的连接方式,而非单个神经元本身。这就是连接主义的核心思想:知识存储在连接中。
这与传统的冯·诺依曼计算机架构形成鲜明对比:
-
冯·诺依曼架构:处理器和内存分离。程序存储在内存中,要执行不同任务,只需更改程序。
-
连接主义架构:机器结构本身就是程序。要执行不同操作,必须改变机器(网络)本身的连接方式。
因此,当前的神经网络模型都是连接主义机器,它们模拟了大脑的结构。
早期神经元模型及其演进
理解了连接主义思想后,我们来看看人们如何尝试用计算模型来模拟神经元。
麦卡洛克-皮茨模型 (1943)
这是第一个神经元的计算模型。该模型将神经元视为布尔阈值单元:
-
接收来自其他神经元的兴奋性或抑制性输入。
-
如果总输入超过阈值,则“激发”(输出1),否则不激发(输出0)。
该模型表明,仅使用这种简单机制,就可以构建任何布尔逻辑门,从而将大脑视为一个巨大的布尔机器。但它没有提供学习规则。
赫布学习规则 (1949)
唐纳德·赫布提出了一个著名的学习规则:“一起激发的神经元,连接在一起。”用公式可表示为:
W_xy = W_xy + η * x * y
其中,x和y是神经元的激活状态(0或1),η是学习率。
局限性:权重只增不减,最终会导致所有连接变得非常强,网络变得无用,因此该规则本质上是不稳定的。
罗森布拉特感知机 (1958)
弗兰克·罗森布拉特提出了“感知机”模型,这是一个更完整的模型。单个感知机单元的结构如下:
-
计算输入的加权和:
z = w1*x1 + w2*x2 + ... + b(b是偏置,即阈值项的负值)。 -
通过一个激活函数(如阶跃函数)输出:如果
z > 0,输出1;否则输出0。
罗森布拉特的关键贡献是引入了期望响应的概念,并提出了一个可证明收敛的学习算法(适用于线性可分问题)。单个感知机可以计算AND、OR、NOT等布尔函数。
局限性:单个感知机无法计算异或(XOR) 函数,因为XOR是线性不可分的。
现代神经网络:多层感知机
既然单个感知机能力有限,本节我们来看看如何通过组合它们来构建更强大的模型。
通过将多个感知机连接成层,就形成了多层感知机。在这种网络中:
-
隐藏神经元:中间层的神经元,其输出模式并非我们直接关心的。
-
输出神经元:最终产生我们感兴趣结果的神经元。
一个关键见解是:单个感知机定义一个线性决策边界(一条直线或超平面)。通过组合多个感知机的输出,可以形成复杂的、非线性的决策边界。
以下是多层感知机(MLP)的强大能力:
-
通用布尔函数:给定任何布尔函数,都可以构造一个MLP来计算它。
-
通用分类器:MLP可以近似任何复杂的决策边界,对输入空间进行划分。例如,要识别数字“2”,MLP可以学习在784维像素空间中划出代表“2”的区域。
-
通用函数逼近器:通过使用适当的激活函数(不仅仅是阶跃函数)和足够多的隐藏单元,MLP可以以任意精度逼近任何连续函数。这意味着神经网络本质上是一个函数,它将输入映射到输出。
因此,无论是语音识别(音频→文本)、图像描述(图像→文字)还是游戏(状态→动作),都可以被看作是一个由神经网络实现的复杂函数。
总结与展望
本节课中,我们一起学习了:
-
神经网络源于对大脑和认知的连接主义建模。
-
早期模型(麦卡洛克-皮茨、赫布、感知机)奠定了基础,但各有局限。
-
现代多层感知机(MLP) 通过组合简单单元,成为了通用函数逼近器,能够进行分类、回归和模拟复杂输入输出关系。
在下一节课中,我们将更深入地探讨神经网络如何计算这些函数,理解“深度”的含义,并讨论神经网络的局限性。
4:如何训练神经网络 🧠
在本节课中,我们将学习如何训练一个神经网络。我们将描述学习问题的本质,介绍感知器规则,并引入经验风险最小化的概念来训练神经网络。
神经网络学习问题
在过去的几节课中,我们介绍了什么是神经网络。它们是这些简单单元的层级组合,这些单元共同协作,能够完成许多复杂的任务。
我们了解到,这些网络实际上可以建模任何布尔函数,这意味着如果你给我一个布尔函数,我可以为其设计一个网络。它们可以以任意精度建模任何分类边界,并且也能建模任何连续值函数。
然而,要使网络能够执行这些操作,它必须具备足够的架构:足够深的网络,其中每一层都足够宽且连接充分。
核心问题:参数学习
给定所有这些,我们发现,在各种人工智能任务中,神经网络是一个神奇的盒子,它接收输入并产生输出。这个盒子本身就是一个函数。
一旦我们意识到这个盒子是一个函数,一些问题就出现了。我们谈论的是数学对象,而当我观察这个AI任务时,输入的是游戏状态,输出的也是游戏状态,这些都不是数学对象。那么,我们如何将它们转换成函数可以实际操作的形式呢?
我们有几个问题需要回答:
-
如何表示输入和输出?输入和输出必须是数学对象,必须是数字或向量。
-
如何组合网络来计算所需的函数?
今天,我们将专注于第二个问题:如何组合执行特定函数的网络。
网络结构与参数
我们定义网络为非常简单的单元组成的网络。最基本的单元是感知器。
感知器接收一组输入,每个输入对应一个权重。它计算输入的加权和,并与一个阈值进行比较。如果超过阈值,它就“激活”。这是感知器最简单的版本。
更进一步,我们可以将感知器视为计算输入的仿射函数(即输入的加权和加上一个偏置),然后应用某种激活函数。最简单的激活函数就是阈值函数。
一旦我们以这种方式分解感知器的操作,激活函数本身可以是任何函数。因此,感知器也可以这样重画:偏置可以表示为一个固定为1的额外输入,该输入的权重就是偏置。
激活函数可以是任何函数:阈值函数、Sigmoid函数或其他类型的函数。这是基本的单元。
但我们实际计算的函数是由这些单元组成的网络。我们假设网络是一个前馈网络,这意味着在处理任何输入时,信息从输入单向流向输出,没有循环。
当我们决定网络结构后,设计网络时需要回答的问题包括架构本身:有多少层,每层有多宽等等。目前,由于我们讨论的是函数逼近,我们将假设我们拥有的任何网络都具有能够建模我们试图建模的特定函数的架构。
现在,网络是一个由这些基本单元组成的层级结构。每个单元都有一些权重和一个偏置。因此,整个网络是一个数学结构,它接收一些输入,最终计算出一个输出。它拥有参数,即网络中神经元的权重和偏置。
因此,你可以用函数形式表示整个网络:F(x; W)。分号后的任何内容表示参数,分号前的内容表示输入。所以这是一个参数化函数,参数是W。
整个网络只是一个参数化函数。因此,当我们谈论学习一个网络时,我们实际上是在谈论学习参数W,因为我们假设网络的架构是给定的。
问题是,我们如何设置这些参数W,以使网络执行你想要它做的任何事情。
从函数逼近到经验风险
我们已经看到,多层感知器可以表示任何函数。这意味着无论你给我什么函数,我总能构造一个MLP来计算这个函数。由于我们特别假设网络架构是给定的,这意味着我们总能选择一组参数W,使网络计算这个函数。
那么,给定一个网络和一个它必须计算的函数,我如何设置网络的参数W,使网络精确地计算这个函数而不是其他东西?
对于非常简单的函数,你可以直接手工设计网络,手动设置参数。但这只适用于最简单的函数。当函数变得更复杂时,你将无法手动设计网络参数。
那么,我们如何为更通用的函数设计网络?如何设置参数值?
可视化学习问题
我们可以这样可视化问题:假设我们有一个目标函数 G(x)。网络本身是一个参数化函数 F(x; W)。当我们谈论训练网络时,我们问自己的问题是:如何设置这些参数W,使得网络计算的函数与目标函数之间的差距最小化?换句话说,如何设置W使得两者之间的“面积”最小化?如果这个面积变为0,那么网络就在计算正确的函数。
为了能够指定这一点,我需要一种量化这个面积的方法。为了量化面积,我首先需要定义一个误差函数,用于量化网络当前计算的结果与你希望它计算的结果之间的差距。
这个阴影面积将是整个输入空间上误差的积分。我们的问题是确定最小化这个误差的W。
实际问题:未知的目标函数
这里可能有什么问题?问题在于,要使这种方法有效,G(x) 必须在所有地方都被指定。而在实践中,我们并不知道 G(x) 是什么。那么我们该怎么办?
我们将对函数进行采样。我们不会知道所有地方的 G(x),而是会收集一堆 x,并在每个 x 处获取对应的 G(x) 值。因此,我们有一堆输入-输出对。
我们只剩下了一组输入-输出对。从这组输入-输出对中,我们将尝试学习这个函数。
回到我们最初的图示,我们现在拥有的是一堆经验性的输入 x 和对应的 G(x) 值。现在,你的网络将在这些输入处计算其他值。因此,我们不是试图最小化整个阴影面积,而是最小化这些红线(误差)在我们获得的样本集合上的平均误差。
收集样本集很容易。你基本上会为你的数据收集输入-输出对,如图像及其标题、语音及其转录。因此,采样函数很容易,我们可以计算这个经验误差估计。
然后,我们可以尝试估计最小化这个经验误差的参数W。
经验误差与真实误差
最小化经验误差是否必然也会最小化网络与你想要的函数之间的实际误差?这是一个我们做出的假设,但并不能保证。
因此,我们的问题归结为:给定一组训练样本(基本上是 G(x) 的样本),我们计算经验误差(即网络输出与训练集合上实际函数输出之间的平均误差)。我们将尝试找到使这个平均误差最小化的网络参数。
我们希望,如果我们做得正确,网络学习到的函数将处处成立。但这只是一个希望,没有保证。
故事梗概
到目前为止,学习神经网络就是确定网络参数(权重和偏置)的问题。网络必须具备足够的能力(即足够的宽度和深度),我们才有机会学习到能够捕捉目标函数的网络。我们假设它具有足够的能力。
因此,学习问题仅仅是确定其参数,以使误差最小化。
理想情况下,你希望优化网络以在所有地方表示所需的函数。但这需要知道所有地方的函数。因此,在实践中,我们做的是抽取输入-输出训练实例。这些训练实例是你的函数的样本。
我们将估计网络参数以拟合这些训练实例上的输入-输出关系。我们希望,当我们这样做时,得到的网络能处处拟合函数 G(x)。
从分类问题开始
让我们从一个简单的任务开始:尝试学习一个分类器,这比学习回归问题更简单。这是最早使用MLP解决的问题之一,因此我们将考虑它,特别是考虑二分类问题,但这可以推广到多分类。
在二分类中,网络的期望输出是什么?是0或1。只有两个类别:0类和1类。那么误差是什么?如果网络的输出是正确的类别,则没有误差。如果网络的输出是错误的类别,则存在误差。
因此,如果你的目标函数是由虚线所示的函数,网络的输出只能是0或1。对于错误的W值,网络函数(以绿色显示)将在某些位置计算错误的输出。
因此,这里的总误差将是网络输出与期望输出不匹配的训练实例的数量。误差是网络输出与期望输出不匹配的训练实例的计数。
当我们想要学习分类器时,我们真正谈论的是学习这些W,使得这些误差的计数最小化。
感知器学习规则
让我们从考虑Rosenblatt最初定义的多层感知器开始。在Rosenblatt的原始模型中,基本单元具有阈值激活。每个感知器计算输入的仿射函数,并将其与0进行比较。如果大于0,则输出为1;如果小于0,则输出为0。
我们现在的问题是用一些训练数据(输入和输出的集合)来训练这个由这些基本单元组成的网络。
最简单的多层感知器模型是什么?最简单的模型就是单个感知器。如果我们只有一个感知器,我们知道它是如何操作的。单个感知器将输入的加权和与阈值进行比较。如果输入大于阈值,则输出为1,否则为0。
因此,输出为1和输出为0之间的边界是输入的加权和恰好等于阈值的地方。这个边界是一个超平面。该函数是一个阶跃函数。
这就是我们想要学习的函数,如果我们只是学习最简单的分类器网络(只有一个单元)。但我们不会被给予整个函数。当我们谈论学习这个函数时,我们真正谈论的是学习权重和阈值。你只会得到一堆训练样本:对于某些输入,输出是0;对于其他输入,输出是1。你只得到那些彩色点(红点和蓝点)。从这些点中,你必须学习整个阶跃函数。
问题本质
这意味着什么?我们想要学习将正例与负例分开的超平面。换句话说,我们想要学习这个边界的方程。而这个边界的方程完全由权重和阈值指定。
让我们看看如何解决这个问题。但在我们这样做之前,这个边界具有 Σ_i w_i x_i + b = 0 的结构,其中 b = -t。这是一种仿射函数。它将是一个超平面,但这个超平面会经过原点吗?
为了简化我们的解释,使用线性函数总是更方便。所以,这是你的仿射函数。我可以这样做:我可以通过添加一个值为1的单一值来增广输入。现在,该输入的对应权重成为偏置。如果我将增广后的输入视为新的输入,这个函数突然变成了线性的。我是如何将仿射函数转换为线性函数的?
转换为线性问题
假设这是x1,这是x2。然后我添加了一个x3,其值始终为1。这意味着我现在完全在x3=1的平面上工作。在这个平面上,我的边界不经过原点。但是,当我观察增广后的输入时,我可以画一个经过原点的超平面,该超平面在这个边界处切割这个平面。
因此,虽然我的超平面与x3=1平面的交线是一条不经过原点的线,但我在这个增广空间中得出的实际决策边界确实经过原点。这样我就将仿射问题转换为了线性问题。现在我可以处理线性问题了。
线性分类器的几何
现在,我们谈论线性函数。我们想要找到一组权重 w_i,使得 Σ_i w_i x_i = 0 表示一个能清晰分隔正例和负例的超平面方程。
我可以稍微不同地写它。Σ_i w_i x_i 可以写成向量形式:w^T x = 0。这就是我的边界。
如果两个向量的内积为0,它们之间的关系是什么?它们是正交的。所以,对于任何W,这表示所有与W正交的X的集合。这是一个经过原点的超平面,包含所有与W正交的向量。
如果我在平面的这一侧(与W相同的一侧)有一个向量,w^T x 的符号将是正的。如果我在另一侧有一个向量 x',w^T x' 将是负的。
因此,我可以简单地运行一个测试:如果 w^T x > 0,则表示空间的这一部分,即类别1;如果 w^T x < 0,则表示类别0。
感知器学习算法的基础
假设我有一个正例实例 x。我可以画出各种将 x 保持在正侧的决策边界。哪个决策边界使 x 离它最远?与它成90度角的那一个。换句话说,对于任何给定的 x,如果 x 是一个正例实例,最优的权重向量是什么?是 x 本身。如果 x 属于负类,最优的权重向量是 -x。
这是感知器学习规则所依据的基本原则。算法如下:给定一堆点,我想找到能清晰分隔红点和蓝点的决策边界。我假设红点和蓝点可以通过一个超平面线性分离。我想找到W,使得对于所有红点 w^T x > 0,对于所有蓝点 w^T x < 0。
有一个闭式解,但最初由Rosenblatt提出的流行解决方案是一种在线算法,即著名的感知器算法。你任意初始化W,然后持续增量修正它,直到分类误差最小化。
我们知道,对于任何正例实例 x,最优权重向量是 x 本身(可能有一些缩放)。对于任何负例实例,最优向量是 -x。
因此,你从某个W开始,开始分类你所有的训练样本。如果你遇到一个被错误分类的正例实例,那么你将权重向量拖向该正例实例的最优权重向量(即向 x 方向拖动),通过简单地将 x 加到 w 上来实现。如果你得到任何被错误分类的负例实例,那么你将权重向量拖向该 x 的最优权重向量(即向 -x 方向拖动),通过从 w 中减去 x 来实现。
算法流程与局限性
感知器学习算法如下:给定一些训练样本集合,初始化W,然后循环遍历训练实例。
只要数据可以被线性边界分离,该算法保证在有限步数内收敛,不会循环。但是,如果类别不是线性可分的,例如,如果红点在蓝侧或蓝点在红侧,算法将永远不会收敛。总是会有一个被错误分类的实例,你将一直追逐自己的尾巴。
所以,当我们只有一个感知器的非常简单的问题时,情况就是这样。即使我假设数据是线性可分的,我也必须进行一些搜索。
扩展到多层网络与组合爆炸
现在,让我们看一些更复杂的情况,比如这个双五边形问题。在双五边形问题中,我知道使用左边的架构,我可以构造一个能精确分类这个双五边形边界的网络。较低层的每个感知器将捕获一个五边形的一条边界。下一层中,一个感知器将捕获一个五边形,另一个将捕获第二个五边形。然后将两者相加,从而得到所需的决策边界。
但是,当你尝试训练这个网络时,你只会得到这些点。你必须学习这个边界。现在,我能应用我们刚刚看到的感知器规则来学习这些边界吗?
为了使这一点更明显,让我们假设某个“先知”告诉了我该层中其他9个感知器的权重。所以我只剩下学习第10个感知器(图中圈出的那个)的权重的问题。训练数据当然是这些红点和蓝点。我能从这些数据中学习那个感知器吗?整个网络是给定的,但“先知”恰好没有给我一个感知器。
我能从这些数据中学习这个感知器吗?感知器学习的要求是数据必须是线性可分的。这里的类别是线性可分的吗?其他感知器的权重定义了特定的边界,然后它可能变成线性的吗?不一定。
如果你想学习这个感知器,你将不得不重新标记一些蓝点,使得两个类别现在变得线性可分。然后,只有到那时,你才能学习感知器。没有其他方法可以学习感知器。
我需要重新标记哪些实例?你怎么知道?记住,如果你错误标记一个实例,整个网络就会出错。你怎么知道?
因此,如果你想真正学习感知器规则,你将不得不开始重新标记一些实例。然后学习边界,正确的解决方案是你不知道要重新标记哪些实例。因此,你将不得不以各种可能的方式重新标记数据实例,并尝试学习决策边界,直到找到一个能为你提供整个训练数据完美分类的重新标记方式。
这意味着,对于单个神经元,我们必须尝试重新标记蓝点的每一种可能方式,使得我们可以学习一条将所有红点保持在一侧的线。而这是在我给了你整个网络的情况下。如果我一开始什么神经元都不知道,只给了你正确的架构,你将不得不为你网络中的每个神经元,以各种可能的方式重新标记数据,这样当你学习每个神经元的边界时,网络的最终输出是完美的。
这是一个组合搜索问题,你无法在合理的时间内完成。它不会奏效。
因此,即使对于这个非常简单的双五边形问题,我们也必须知道每个训练实例中每个神经元的期望输出(基本上是如何标记它们)。我们必须这样做,以便当你将它们全部组合时,整体的决策边界是双五边形。这是一个组合优化问题。如果你在重新标记时犯了一个错误,你的整个分类器就会变成垃圾。
问题的核心
这就是问题所在:使用感知器规则训练这个网络是一个组合优化问题。我们不知道每个训练实例中每个神经元的输出。我们需要重新标记它们并在空间中搜索,因此我们还必须确定每个训练实例中每个神经元的正确输出,这至少是输入大小的指数级。
因此,这不是一个可行的解决方案。这实际上使多层感知机的研究停滞了很长时间。人们意识到,早期的原始感知器和MLP基于简单的线性分类器和阈值激活。很快人们就意识到,学习单个感知器的问题可能是可处理的,但如果你想学习整个网络,你就会遇到这个无法解决的组合优化问题。
因此,人们提出了各种贪婪解决方案,如Adaline、Madaline等。这些方法持续了将近三十年,直到Paul Werbos(在MIT)发现了一种替代方法(在70年代初),但直到15年后,Jeff Hinton才认识到Werbos论文的价值。
解决方案:可微性与代理损失
那么解决方案是什么?首先,到目前为止的故事是:学习网络就是学习权重和偏置以计算目标函数的问题,假设网络具有足够的能力。在实践中,我们通过使网络匹配从目标函数中抽取的训练实例的输入-输出关系来学习网络。
线性决策边界可以由单个感知器学习。如果类别是线性可分的,可以在线性时间内学习。对于非线性决策边界,我们需要感知器网络,但使用阈值函数激活训练MLP将需要知道每个训练实例中每个感知器的输入-输出关系。这些必须作为训练的一部分来确定。对于阈值激活,这是一个NP问题,具有NP复杂度。
因此,认识到训练整个MLP是一个组合优化问题,使神经网络的发展停滞了十多年,直到我们有了一个小小的洞见。
阈值激活的问题
那个洞见是什么?问题在于,我们真正试图计算的是误差:网络正确的频率是多少?错误的频率是多少?你有一堆训练输入,对于这些输入,输出必须是0或1。然后,在每个输入处,你可以计算网络的输出。网络的输出可能是正确的或错误的,我们计算它错误分类的次数,然后设置参数。
但是,假设我有这个训练数据 x1 到 x5,虚线显然是正确的决策边界。假设我选择构建我的网络,使其学习绿色函数。绿色函数在某个点从0变为1,它犯了两个错误。现在,如果我将这个阈值(从0变为1的点)向左移动一点,错误的数量会改变吗?如果我向右移动一点,错误的数量会改变吗?我必须移动很多,它必须跨越一个训练点。如果我移动一点点,错误的数量不会改变。这就是实际问题。
同样,如果我试图在多维空间中学习这种感知器,我可能从一个初始边界开始。它会错误分类一些实例。如果我稍微旋转边界,在边界实际跨越并分类一个训练点之前,错误总数不会改变。因此,我的参数的微小变化不会导致误差的任何变化。这就是为什么我们真的无法使用增量步骤来优化。没有迹象表明我应该朝这个方向还是那个方向旋转权重,因为朝这个方向或那个方向
5:神经网络优化与梯度下降 🧠
在本节课中,我们将学习如何训练神经网络,这本质上是一个函数优化问题。我们将从基础的导数概念出发,逐步理解梯度下降算法,并了解如何将其应用于最小化神经网络的损失函数。
1. 函数最小化与导数回顾
上一节我们介绍了神经网络训练的核心是经验风险最小化。本节中,我们来看看如何通过优化方法找到使损失函数最小的参数。
1.1 导数的本质
给定一个函数 y = f(x),在任意点 x 处,如果我们对 x 施加一个微小的扰动 Δx,会导致 y 产生一个微小的变化 Δy。导数 f'(x) 被定义为连接 Δx 和 Δy 的乘性因子。
公式:
Δy ≈ f'(x) * Δx
当 x 和 y 都是标量时,我们常写作 dy/dx。导数 f'(x) 的符号表明了函数的变化趋势:
-
如果
f'(x) > 0,函数在x处递增。 -
如果
f'(x) < 0,函数在x处递减。 -
在函数从递增转为递减(局部极大值)或从递减转为递增(局部极小值)的转折点,导数为零。
1.2 多变量函数的导数:梯度
当输入 x 是一个向量时,函数 y = f(x) 的导数定义依然不变:Δy 是 Δx 的线性函数。
公式:
Δy = ∇f(x)^T * Δx
其中,∇f(x) 称为函数在 x 处的梯度。它是一个向量,其每个分量是函数 f 对相应输入分量的偏导数。
梯度的几何意义:
梯度指向函数值增长最快的方向。其大小表示增长率。因此,负梯度 -∇f(x) 指向函数值下降最快的方向。
梯度的另一个性质:梯度始终垂直于函数的等高线(即函数值相同的点构成的集合)。
1.3 海森矩阵与二阶信息
对于多变量函数,海森矩阵 H 描述了函数的曲率。其第 (i, j) 个元素是二阶偏导数 ∂²f / (∂x_i ∂x_j)。
海森矩阵的特征值与优化:
-
在局部极小值点,海森矩阵的所有特征值均为正。
-
在局部极大值点,所有特征值均为负。
-
如果特征值有正有负,则该点是一个鞍点。
2. 梯度下降算法 📉
对于复杂的函数,我们无法直接求解 ∇f(x) = 0 来找到最小值。梯度下降提供了一种迭代逼近的方法。
2.1 算法核心思想
我们从参数的初始猜测 x₀ 开始,反复沿负梯度方向(即函数下降最快的方向)移动一小步,直到函数值不再显著变化。
更新公式:
x_{k+1} = x_k - η * ∇f(x_k)
其中:
-
x_k是第k次迭代的参数值。 -
η是学习率,控制步长大小。 -
∇f(x_k)是函数在x_k处的梯度。
2.2 算法行为与挑战
梯度下降的行为取决于函数形状:
-
对于凸函数(如碗状),梯度下降能稳定收敛到全局最小值。
-
对于非凸函数,算法可能陷入局部最小值、鞍点或平坦区域。
停止准则:通常不直接检查梯度是否为零,而是监控函数值 f(x) 的变化。当连续迭代中函数值下降非常微小时,即可停止。
3. 应用于神经网络训练 🔧
现在,我们将梯度下降的思想应用于神经网络的训练。
3.1 问题重述
给定训练集 {(x_i, d_i)},我们定义损失函数 L(W),它是网络输出 f(x_i; W) 与期望输出 d_i 之间差异的平均值。目标是找到参数 W 以最小化 L(W)。
训练过程:
-
初始化网络参数
W。 -
重复以下步骤直至收敛:
a. 计算当前参数下,损失函数关于所有参数的梯度
∇L(W)。b. 沿负梯度方向更新参数:
W := W - η * ∇L(W)。
直观理解:梯度下降检查每个参数 w_i,判断“增加这个参数会使损失增大还是减小”。根据结果,相应地增加或减少该参数。
3.2 网络组件与数据表示
为了具体计算损失和梯度,我们需要定义网络的各个部分。
神经元结构:典型的神经元先计算输入的线性加权和(仿射变换),再通过一个非线性激活函数。
z = w^T * a + b
y = g(z)
常见激活函数包括 Sigmoid、Tanh、ReLU 及其平滑版本 Softplus。
向量激活函数:有些激活函数同时处理整个层的神经元输出。最典型的是 Softmax,它将一组实数 z_i 转换为一个概率分布 y_i:
y_i = e^{z_i} / Σ_j e^{z_j}
Softmax 的输出所有分量和为1,且每个分量非负,常用于多分类网络的输出层。
输入与输出表示:
-
输入
x:必须是数值向量(如图像像素、语音特征、文本嵌入)。 -
输出
y与目标d:-
回归任务:
d和y是实值标量或向量。损失函数常使用均方误差。 -
二分类任务:网络输出一个标量
y(如使用 Sigmoid 表示正类概率),目标d为 0 或 1。 -
多分类任务:网络输出一个向量
y(使用 Softmax 表示各类概率),目标d使用 独热编码(一个分量是1,其余为0)。
-
3.3 损失函数的选择
损失函数必须是可微的,这样才能计算梯度。不同任务对应不同的损失函数。
回归任务(L2损失):
L = 1/2 * ||y - d||²
引入 1/2 是为了使损失对 y 的导数形式更简洁:∂L/∂y = y - d。
分类任务(交叉熵损失):
-
二分类:
L = -[d * log(y) + (1-d) * log(1-y)] -
多分类:
L = -log(y_c),其中c是目标类别索引。
为什么使用交叉熵而非均方误差?
交叉熵损失在概率预测中具有更好的性质。例如,当网络对错误类别给出100%置信度时,交叉熵损失会趋于无穷大,迫使网络快速修正错误。而均方误差对此惩罚不足。
一个重要性质:对于使用 Softmax 输出层和交叉熵损失的多分类网络,损失 L 关于 Softmax 层输入 z 的梯度恰好是 y - d。这与回归问题中 L2 损失的梯度形式一致,极大地简化了反向传播的计算。
总结
本节课中我们一起学习了:
-
优化基础:回顾了导数、梯度、海森矩阵的概念及其在寻找函数极值点中的作用。
-
梯度下降:掌握了这一核心迭代优化算法,它通过沿负梯度方向更新参数来最小化函数。
-
神经网络训练框架:将梯度下降应用于神经网络,明确了训练即最小化损失函数的过程。
-
关键组件:了解了神经元结构、激活函数(特别是 Softmax)、数据表示(独热编码)以及针对回归和分类任务的不同损失函数(L2损失和交叉熵损失)。
下一节课,我们将深入探讨如何高效地计算神经网络中复杂的梯度——即反向传播算法。
6:神经网络训练与反向传播 🧠
在本节课中,我们将学习如何训练神经网络。核心是通过经验风险最小化来最小化损失函数,并使用梯度下降算法来更新网络参数。为了实现梯度下降,我们需要计算损失函数相对于每个网络参数的梯度,而反向传播正是高效计算这些梯度的关键算法。
概述:训练神经网络的框架
训练神经网络的目标是找到一组参数(权重和偏置),使得网络在训练数据上的预测误差最小。我们通过最小化一个称为损失函数的量来实现这一点。损失函数衡量了网络输出与期望输出之间的差异。
具体来说,给定一个包含输入-输出对 (X, Y) 的训练集,我们定义损失函数 L(W) 为所有训练实例上差异的平均值。这里的差异 D 衡量了网络实际输出与期望输出之间的距离。损失 L 是网络参数 W 的函数。
我们的目标是找到使 L(W) 最小的 W。这通过梯度下降完成:我们初始化参数,然后反复沿损失函数梯度的反方向更新参数。
梯度下降更新公式:
W_new = W_old - η * ∇L(W_old)
其中 η 是学习率,∇L(W) 是损失函数关于参数 W 的梯度。
为了执行梯度下降,我们必须计算梯度 ∇L(W)。由于损失是单个训练实例差异的平均值,因此梯度也是这些单个差异梯度的平均值。所以,核心问题归结为:如何计算单个训练实例的差异相对于任意网络参数 W_ij 的梯度? 这就是本节课和下一节课的重点。
预备知识:微积分与链式法则回顾
在深入反向传播之前,我们先回顾一些基本的微分规则,并用一种直观的“影响图”来表示。
基本导数与影响图
对于一个函数 y = f(x),导数 dy/dx 描述了 x 的微小变化 Δx 会导致 y 产生多大的变化 Δy。对于足够小的 Δx,有:
Δy ≈ (dy/dx) * Δx
我们可以用“影响图”来表示:变量 x 通过一条边影响变量 y,这条边的“权重”就是导数 dy/dx。
标量函数图示:
x --(dy/dx)--> y
多变量函数与偏导数
对于多变量函数 y = f(x1, x2, ..., xn),每个输入变量 xi 都通过一条边影响 y,边的权重是偏导数 ∂y/∂xi。y 的总变化是所有输入变化的加权和:
Δy ≈ Σ_i (∂y/∂xi) * Δxi
多变量影响图:
x1 --(∂y/∂x1)--> y
x2 --(∂y/∂x2)--> y
...
xn --(∂y/∂xn)--> y
链式法则
对于复合函数 y = f(g(x)),存在中间变量 g。影响图变为:
x --(dg/dx)--> g --(dy/dg)--> y
根据图示,x 对 y 的总体影响是沿着路径权重的乘积:
Δy ≈ (dy/dg) * (dg/dx) * Δx
因此,导数 dy/dx = (dy/dg) * (dg/dx)。这就是链式法则。
分布式链式法则
当多个中间变量都依赖于同一个变量,并共同影响最终输出时,需要使用分布式链式法则。例如,y = f(g1(x), g2(x), ..., gn(x))。
影响图如下:
x --(dg1/dx)--> g1 --(∂y/∂g1)--> y
x --(dg2/dx)--> g2 --(∂y/∂g2)--> y
...
x --(dgn/dx)--> gn --(∂y/∂gn)--> y
x 对 y 的总体影响是所有可能路径贡献的总和:
dy/dx = Σ_i (∂y/∂gi) * (dgi/dx)
前向传播:计算网络所有中间值
为了计算梯度,我们首先需要知道网络在给定输入和当前参数下的所有中间激活值。这个过程称为前向传播。
考虑一个具有 L 层的神经网络。我们使用以下符号:
-
y^(0):网络输入x。 -
对于第
l层 (l=1...L):-
z^(l):该层神经元的仿射(加权和)值向量。z_j^(l)表示第l层第j个神经元的仿射值。 -
W^(l):连接第l-1层到第l层的权重矩阵。 -
b^(l):第l层的偏置向量。 -
y^(l):第l层的输出(激活值)向量。y_j^(l) = f(z_j^(l)),其中f是激活函数。
-
前向传播的伪代码如下:
-
设置输入:
y^(0) = x -
对于每一层
l = 1到L:a. 计算仿射值:
z^(l) = W^(l) * y^(l-1) + b^(l)b. 应用激活函数:
y^(l) = f^(l)(z^(l)) -
网络最终输出为
y^(L)。
通过前向传播,我们得到了网络中每一个 z 和 y 的值,这些值在后续计算梯度时是必需的。
反向传播:计算梯度
现在我们有了所有中间值,可以计算损失相对于每个参数的梯度了。我们从网络的输出层开始,逆向计算到输入层,因此这个过程被称为反向传播。
我们的目标是计算对于单个训练实例的差异 D 相对于任意参数 W_ij^(l) 或 b_j^(l) 的梯度。
核心思想:逆向链式法则
我们从输出层开始。首先计算差异 D 相对于网络最终输出 y^(L) 的梯度 ∂D/∂y^(L)。这个梯度取决于我们具体使用的差异函数(如均方误差、交叉熵等),是已知的。
然后,我们一步步向后(反向)计算:
-
计算
∂D/∂z^(L):利用链式法则,∂D/∂z^(L) = (∂D/∂y^(L)) * (∂y^(L)/∂z^(L))。其中∂y^(L)/∂z^(L)就是激活函数f在点z^(L)处的导数f'(z^(L))。 -
计算参数梯度:
-
对于输出层权重:
∂D/∂W^(L) = (∂D/∂z^(L)) * (∂z^(L)/∂W^(L))。由于z^(L) = W^(L)*y^(L-1) + b^(L),所以∂z^(L)/∂W^(L)就是前一层的输出y^(L-1)。因此,∂D/∂W^(L) = (∂D/∂z^(L)) * (y^(L-1))^T(注意维度匹配)。 -
对于输出层偏置:
∂D/∂b^(L) = ∂D/∂z^(L),因为∂z^(L)/∂b^(L)是单位矩阵。
-
-
计算对前一层的“误差信号”:为了继续反向传播,我们需要知道
D对前一层的输出y^(L-1)的梯度。∂D/∂y^(L-1) = (W^(L))^T * (∂D/∂z^(L))。这可以理解为当前层的梯度∂D/∂z^(L)通过权重矩阵W^(L)反向传播到了前一层的输出。 -
重复步骤:现在我们将
y^(L-1)视为新的“输出”,重复步骤1-3,计算∂D/∂z^(L-1)、∂D/∂W^(L-1)、∂D/∂b^(L-1)以及∂D/∂y^(L-2),依此类推,直到传播到第一层。
向量化形式的反向传播
在实际实现中,我们使用向量和矩阵运算,效率更高。对于第 l 层,反向传播的向量化计算如下:
假设我们已经计算出了 ∂D/∂z^(l)(记作 δ^(l))。
-
计算权重梯度:
∂D/∂W^(l) = δ^(l) * (y^(l-1))^T -
计算偏置梯度:
∂D/∂b^(l) = δ^(l) -
计算对前一层的误差:
∂D/∂y^(l-1) = (W^(l))^T * δ^(l) -
计算前一层的
δ:δ^(l-1) = (∂D/∂y^(l-1)) ⊙ f'(z^(l-1)),其中⊙表示逐元素乘法。
反向传播的伪代码如下(向量形式):
-
前向传播,保存所有
z^(l)和y^(l)。 -
计算输出层误差:
δ^(L) = (∂D/∂y^(L)) ⊙ f'(z^(L)) -
对于层
l = L到2(反向):a. 计算权重梯度:
∇W^(l) = δ^(l) * (y^(l-1))^Tb. 计算偏置梯度:
∇b^(l) = δ^(l)c. 向上一层传播误差:
δ^(l-1) = (W^(l))^T * δ^(l) ⊙ f'(z^(l-1)) -
最终也可以计算第一层的参数梯度。
注意,对于整个训练集的损失 L,其梯度是每个训练实例计算出的梯度 ∇D 的平均值。因此,在实际训练中,我们通常在一个批次(batch)的数据上计算平均梯度,然后用它来更新参数。
特殊情况与注意事项
反向传播算法依赖于一些假设,当这些假设不成立时,需要特殊处理。
1. 向量激活函数(如Softmax)
对于标准的逐元素标量激活函数(如Sigmoid、ReLU),∂y/∂z 是一个对角矩阵。但对于像Softmax这样的向量激活函数,每个输出 y_i 都依赖于所有输入 z_j。因此,其雅可比矩阵 ∂y/∂z 是一个满矩阵,而不是对角阵。
在反向传播中,计算 δ = (∂D/∂y) * (∂y/∂z) 时,就需要进行矩阵乘法,而不是简单的逐元素乘法。对于Softmax与交叉熵损失结合这种常见情况,可以推导出简洁的梯度形式。
2. 不可微的激活函数(如ReLU, Max)
-
ReLU:在
z=0处不可微,因为左导数为0,右导数为1。在实践中,我们通常约定在z=0处的导数(次梯度)为一个值,例如0或1。常用约定是:f'(z) = 1 if z > 0 else 0。 -
Max函数:例如
y = max(z1, z2, ..., zn)。其导数对于最大值对应的输入为1,对于其他输入为0。如果最大值不唯一(平局),则需要定义次梯度。
对于这些情况,只要函数是连续的(或分段连续),我们仍然可以使用一个定义的(次)梯度来进行反向传播,这在实践中通常是有效的。
总结
本节课我们一起学习了神经网络训练的核心机制:
-
训练目标:通过最小化损失函数(训练集上的平均差异)来优化网络参数。
-
优化方法:使用梯度下降法迭代更新参数。
-
梯度计算:通过反向传播算法高效计算损失函数相对于所有参数的梯度。反向传播本质上是微积分中链式法则在计算图上的高效应用。
-
计算过程:
-
前向传播:计算网络在给定输入下的所有中间输出。
-
反向传播:从输出层开始,逆向计算梯度,并利用这些梯度更新每一层的权重和偏置。
-
-
实现形式:使用向量和矩阵运算可以极大地简化并加速前向和反向传播的过程。
反向传播是训练深度神经网络的基石,理解其原理对于掌握深度学习至关重要。在接下来的课程中,我们将探讨与训练相关的其他重要主题,如优化器、初始化策略和正则化。
7:神经网络训练与优化 🧠
在本节课中,我们将要学习神经网络训练的核心过程——梯度下降法,并深入探讨其在实际应用中的收敛性、挑战以及一些高效的优化算法。我们将从回顾反向传播开始,分析梯度下降可能遇到的问题,并介绍如何通过调整学习率和使用更先进的优化器来改善训练效果。
神经网络训练回顾
上一节我们介绍了如何使用反向传播计算梯度。本节中,我们来看看梯度下降法如何利用这些梯度来更新网络权重,并开始探讨其有效性。
神经网络是通用函数逼近器,只要架构合适,它们可以模拟任何复杂函数。我们必须通过训练来学习权重,使网络能够逼近目标函数。训练的目标是最小化在训练集上定义的损失函数。
然而,最小化训练集上的损失,是否保证网络学到了正确的函数?我们真正希望最小化的是模型在整个数据分布上的期望误差,而我们实际做的只是在一小部分训练样本上最小化经验风险。我们使用梯度下降法进行这种最小化,而梯度则通过反向传播计算。
以下是训练的基本步骤:
-
给定一组训练点,定义在所有训练点上的损失函数,即训练集上的平均误差。
-
初始化所有网络参数。
-
迭代计算损失函数相对于所有网络参数的梯度。
-
沿着梯度的反方向更新所有网络参数。
反向传播用于计算关键的梯度项:损失相对于网络参数的导数。
反向传播规则与向量化更新
在上一节中,我们推导了反向传播的规则。本节中,我们通过向量化的形式来回顾这些更新规则,以便更清晰地理解信息在网络中的流动。
在网络中,每一层的计算如下:
-
线性变换:
Z_k = W_k * Y_{k-1} + b_k- 其中
Y_{k-1}是前一层的输出向量,W_k是权重矩阵,b_k是偏置向量,Z_k是当前层的净输入。
- 其中
-
激活函数:
Y_k = f(Z_k)- 其中
f是非线性激活函数。
- 其中
通过这两个规则,我们可以从输入层逐步前向传播到输出层。最终,网络的输出会与期望输出进行比较,计算损失。每一个 Y_k 最终都会影响损失值。
在反向传播中,我们计算损失相对于各层参数的梯度。核心是利用链式法则:
-
损失
L相对于Z_k的梯度:∂L/∂Z_k = (∂L/∂Y_k) * (∂Y_k/∂Z_k)- 这里
∂Y_k/∂Z_k是激活函数的导数构成的雅可比矩阵。
- 这里
-
损失
L相对于Y_{k-1}的梯度:∂L/∂Y_{k-1} = (∂L/∂Z_k) * W_k^T -
损失
L相对于参数W_k和b_k的梯度:-
∂L/∂W_k = Y_{k-1}^T * (∂L/∂Z_k) -
∂L/∂b_k = ∂L/∂Z_k
-
通过这种方式,我们可以从输出层开始,逐层反向迭代,计算出所有参数的梯度。
梯度下降的局限性与挑战
我们已经知道如何使用反向传播计算梯度并进行参数更新。本节中,我们来看看梯度下降法本身是否存在局限,以及它是否总能找到最优解。
首先,反向传播这个术语有时被滥用来指代整个梯度下降算法,但它严格来说只是用于计算梯度的一部分。真正的问题是:梯度下降总能找到损失函数的正确最小值吗?
在分类问题中,我们最小化的损失函数(如交叉熵)只是分类错误率的一个可微代理。最小化这个代理损失,并不保证最小化真正的分类错误率。
考虑一个简单的线性可分二分类例子。如果使用感知机学习规则,它能找到一个完美的决策边界。但如果使用梯度下降最小化一个可微损失函数(如逻辑损失),当我们在正确类别一侧很远的地方加入一个“干扰”样本时,梯度下降可能不会为了完美分类这一个样本而大幅改变决策边界,从而容忍一个错误。
-
感知机规则:具有低偏差(如果解存在,它就能找到),但高方差(对单个数据点非常敏感,解可能剧烈变化)。
-
梯度下降:具有较高的偏差(可能无法找到完美解),但低方差(对噪声和异常值不敏感,解更稳定)。
因此,使用反向传播梯度训练的神经网络分类器,通常比训练数据上的最优分类器具有更低的方差。我们更偏好这种一致性而非对训练集的完美拟合。
另一个挑战是损失函数的形态。损失曲面可能非常复杂,存在许多局部最小值和鞍点。梯度下降可能会陷入某个局部最小值或鞍点而无法到达全局最小。
-
鞍点:在某些方向上是极小值,在另一些方向上是极大值,梯度为零。
-
对于大型网络:理论表明,许多局部最小值在损失值上彼此接近,并且与全局最小值相差不大,陷入一个局部最小值可能并非灾难。
收敛性分析:从凸函数到神经网络
梯度下降法一定会收敛吗?如果收敛,速度如何?由于神经网络是复杂的非线性函数,直接分析其训练动态非常困难。因此,我们通常先分析更简单的凸函数,以期获得一些洞见。
一个凸函数的定义是:连接函数图像上任意两点的线段都位于函数图像上方。凸函数具有良好的性质,例如只有一个全局最小值。
我们首先分析最简单的凸函数:二次函数。一个标量二次函数 f(x) = 1/2 * a * x^2 + b * x + c(a > 0)是一个开口向上的抛物线,有唯一最小值。
梯度下降的更新规则是:x_{k+1} = x_k - η * f'(x_k),其中 η 是学习率。
-
最优学习率:当
η = 1 / f''(x_k) = 1/a时,梯度下降一步就能到达最小值。 -
学习率过小:
η < 1/a,会收敛,但需要更多步数。 -
学习率过大:
η > 2/a,可能导致迭代发散。 -
学习率在临界值:
η = 2/a,会在最小值两侧无限振荡。
对于非二次函数,我们可以在当前点 x_k 处用泰勒级数进行二次近似:f(x) ≈ f(x_k) + f'(x_k)(x - x_k) + 1/2 f''(x_k)(x - x_k)^2。此时,局部最优步长就是 1 / f''(x_k)。因此,选择合适的学习率至关重要。
多维情况与条件数问题
上一节我们分析了标量函数的收敛性。本节中,我们将其推广到多维情况,这会引入新的挑战——不同方向可能具有不同的最优学习率。
考虑一个轴对齐的二元二次函数:f(x1, x2) = 1/2 * a11 * x1^2 + 1/2 * a22 * x2^2。它的等高线是椭圆。
-
沿
x1方向的最优步长是1 / a11。 -
沿
x2方向的最优步长是1 / a22。
在标准梯度下降中,我们对所有参数使用统一的学习率 η。这会导致问题:
-
如果
η接近1/a11(x1方向最优),但对x2方向来说可能太大(如果a22很小)或太小(如果a22很大)。 -
结果可能是:一个方向收敛很快,另一个方向收敛极慢甚至发散。
为了保证所有方向都收敛,学习率 η 必须小于最陡峭方向(对应最大 a,即最小最优步长)最优步长的两倍。但这意味着在平坦方向上的学习会非常缓慢。
条件数(最大特征值与最小特征值之比)衡量了曲率在不同方向上的差异程度。条件数越大(椭圆越扁),标准梯度下降的收敛就越慢。为了快速收敛,我们希望条件数接近1(等高线接近圆形)。
对于非二次函数,海森矩阵(二阶导数矩阵)的特征值决定了不同方向的曲率。同样,统一学习率会受到条件数的制约。
学习率调度与进阶优化算法
面对收敛速度慢或可能陷入不良局部最小值的问题,我们有哪些策略?本节介绍两种核心思路:动态调整学习率,以及为不同参数维度使用不同的学习率。
1. 学习率衰减
与其使用固定学习率,不如在训练过程中动态减小它。
-
思路:初期使用较大的学习率,有助于快速前进并可能跳出尖锐的局部最小值或鞍点;后期减小学习率,有助于稳定地收敛到宽阔的谷底。
-
方式:可以线性衰减、指数衰减、按步衰减等。
2. 为不同维度适配不同学习率
这是解决条件数问题的根本方法。目标是让每个参数都有自己的“学习步调”。以下是两个经典算法:
-
RProp(弹性反向传播)
RProp 完全摒弃了梯度的大小,只利用其符号,并为每个权重维护一个独立的步长值。
-
核心规则:
-
如果本次梯度的符号与上一次相同,说明方向正确,则增大该权重的步长(乘以一个因子
α > 1)。 -
如果本次梯度的符号与上一次相反,说明可能越过了极值点,则减小该权重的步长(乘以一个因子
0 < β < 1),并反转更新方向(或退回上一步)。
-
-
优点:非常简单高效,对损失函数形状假设少,常能收敛到宽阔的极小值点。
-
-
QuickProp(快速传播)
QuickProp 尝试近似每个参数的二阶导数(海森矩阵的对角线),从而使用类牛顿法的更新。
-
思路:通过比较连续两次迭代的梯度和参数变化,来估计该维度上的曲率,并据此计算一个自适应的步长。
-
优点:收敛速度通常快于标准梯度下降。
-
这些方法的核心思想是解耦不同维度的更新,根据每个参数自身的梯度行为来调整其步长。
动量法及其变体
除了RProp和QuickProp,另一类非常重要的优化算法是动量法。它通过引入“速度”的概念,来平滑梯度更新并加速收敛。
动量法(Momentum)
动量法模拟了物理中的动量概念。它维护一个速度向量 v,该向量是过去梯度的指数加权移动平均。
-
更新规则:
v_t = γ * v_{t-1} + η * ∇J(θ_t)θ_{t+1} = θ_t - v_tγ是动量系数(通常设为0.9),控制历史梯度的影响。
-
作用:
-
在梯度方向持续一致的维度上,速度会累积,更新加快。
-
在梯度方向频繁改变的维度上,梯度会相互抵消,速度减小,更新变慢。
-
这相当于在各个维度上产生了自适应的学习率,有助于缓解条件数问题,并帮助穿过狭窄的鞍点区域。
-
Nesterov 加速梯度(NAG)
NAG 是动量法的一个改进版本,它具有“前瞻性”。
-
更新规则:
v_t = γ * v_{t-1} + η * ∇J(θ_t - γ * v_{t-1})θ_{t+1} = θ_t - v_t -
区别:NAG 在计算当前梯度时,不是基于当前位置
θ_t,而是基于“如果按照现有动量再前进一步”的预估位置θ_t - γ * v_{t-1}。这使得更新更具前瞻性,能对梯度变化做出更灵敏的响应,通常比标准动量法收敛更快。
动量法和NAG是深度学习中最常用且有效的优化器之一,我们将在讨论随机梯度下降时进一步探讨。
总结
本节课中,我们一起深入探讨了神经网络训练的核心——梯度下降优化法。
我们首先回顾了反向传播如何计算梯度。然后分析了梯度下降的局限性:它最小化的是真实目标的一个可微代理,因此可能错过某些理论上的最优解,但这往往能带来更稳定、方差更低的模型,这有时是有益的。
接着,我们研究了收敛性问题。从简单的凸二次函数分析中,我们发现学习率的选择至关重要,存在一个最优值。在多维情况下,不同参数方向可能有不同的最优学习率,而标准梯度下降的统一学习率会受制于最坏方向的条件数,导致收敛缓慢。
为了克服这些挑战,我们介绍了多种策略:
-
学习率衰减:在训练中动态降低学习率,以平衡探索与收敛。
-
维度解耦的优化器:
-
RProp:仅使用梯度符号,为每个权重独立调整步长,简单而强大。
-
QuickProp:近似二阶信息,实现更快的收敛。
-
-
动量法:通过累积历史梯度来平滑更新,在一致方向上加速,在振荡方向上减速,有效缓解条件数问题。其变体 Nesterov 加速梯度 通过前瞻性计算进一步提升了性能。
理解这些优化算法的原理和权衡,对于有效训练深度神经网络至关重要。在接下来的课程中,我们将把这些知识应用到更实际的随机梯度下降环境中。
8:训练神经网络(续)📚
在本节课中,我们将继续学习如何训练神经网络。我们将深入探讨增量更新、随机梯度下降、小批量训练以及更高级的优化算法,如动量法和Adam。这些技术对于高效、稳定地训练深度神经网络至关重要。
从批量更新到增量更新 🔄
上一节我们介绍了通过梯度下降最小化损失函数来训练神经网络。这种方法通常需要处理整个训练集来计算平均梯度,然后才更新一次参数,这被称为批量更新。
然而,当训练集非常庞大(例如数十亿个样本)时,每次迭代都处理所有数据在计算上是不可行的。此外,批量更新可能收敛缓慢。
本节中我们来看看一种更实用的方法:增量更新。其核心思想是,我们不等待处理完所有数据,而是每看到一个训练样本(或一小批样本)就立即更新一次网络参数。
以下是增量更新的基本步骤:
-
随机打乱训练数据的顺序。
-
依次遍历每个训练样本(或小批量)。
-
对于每个样本,计算其损失相对于网络参数的梯度。
-
立即使用这个梯度(乘以学习率)来更新网络参数。
-
重复此过程多次(多个轮次)。
这种方法计算效率更高,并且通常能更快地减少训练初期的损失。
随机梯度下降(SGD)🎲
当增量更新的批量大小为1时,我们称之为随机梯度下降。之所以称为“随机”,是因为我们以随机顺序呈现训练样本。
SGD的更新公式可以表示为:
θ = θ - η * ∇J(θ; x_i, y_i)
其中:
-
θ代表网络参数。 -
η是学习率。 -
∇J(θ; x_i, y_i)是单个训练样本(x_i, y_i)的损失梯度。
关键点:
-
随机性:必须随机化样本顺序,否则可能导致循环振荡,无法收敛。
-
学习率衰减:为了确保最终收敛,学习率必须随着迭代进行而衰减。一个常见的要求是学习率序列
{η_t}满足:∑ η_t = ∞且∑ η_t^2 < ∞。例如,η_t = 1/t就满足这个条件。 -
方差大:由于每次更新只基于一个样本,梯度估计的方差很大。这导致损失曲线波动剧烈,但有时能帮助模型跳出局部最优解。
小批量梯度下降 ⚖️
SGD虽然计算快,但梯度估计的高方差可能导致收敛不稳定,甚至收敛到较差的解。一个自然的折中方案是使用小批量梯度下降。
在小批量梯度下降中,我们不是处理单个样本,也不是处理整个数据集,而是每次处理一小批(例如32、64、128个)随机样本。
其更新公式为:
θ = θ - η * (1/B) * ∑ ∇J(θ; x_i, y_i), 其中求和针对当前小批量中的B个样本。
优点:
-
降低方差:相对于SGD,基于一批样本的梯度估计方差更小,更新方向更稳定。
-
硬件友好:现代GPU等硬件可以高效并行处理小批量数据,计算速度远快于逐个处理样本。
-
收敛更优:通常能比SGD收敛到更好的解,同时比批量更新更快。
在实践中,小批量大小通常设置为你的硬件(如GPU内存)所能支持的最大值,以在降低方差和利用并行计算之间取得最佳平衡。
高级优化算法:动量法与Adam 🚀
无论是SGD还是小批量梯度下降,在复杂损失曲面上都可能遇到问题,例如在峡谷状区域振荡或在平坦区域缓慢前进。高级优化算法旨在解决这些问题。
动量法(Momentum)
动量法的灵感来自于物理学中的动量。它引入了一个速度变量 v,用于累积过去的梯度信息。更新规则如下:
v_t = β * v_{t-1} + (1 - β) * ∇J(θ_t)
θ_{t+1} = θ_t - η * v_t
其中 β 是动量系数(通常为0.9),控制着历史梯度的影响程度。
作用:
-
在梯度方向持续一致的维度上,更新速度会加快。
-
在梯度方向频繁改变的维度上,更新会因正负抵消而减缓。
-
这有助于加速收敛并减少振荡。
RMSProp
RMSProp 自适应地调整每个参数的学习率。它为每个参数维护一个梯度平方的指数移动平均值,并以此调整该参数的学习步长。
E[g^2]_t = γ * E[g^2]_{t-1} + (1 - γ) * (∇J(θ_t))^2
θ_{t+1} = θ_t - η / (√(E[g^2]_t + ε)) * ∇J(θ_t)
其中 γ 是衰减率,ε 是一个很小的数(如1e-8)防止除零。
作用:
-
在梯度大的参数方向,降低学习率,避免震荡。
-
在梯度小的参数方向,提高学习率,加速更新。
-
解决了不同参数尺度差异大的问题。
Adam(自适应矩估计)
Adam 结合了动量法和RMSProp的思想,可以说是当前最流行、默认的优化器之一。它同时计算梯度的一阶矩(均值,类似动量)和二阶矩(未中心化的方差,类似RMSProp)。
m_t = β1 * m_{t-1} + (1 - β1) * g_t
v_t = β2 * v_{t-1} + (1 - β2) * g_t^2
m̂_t = m_t / (1 - β1^t)
v̂_t = v_t / (1 - β2^t)
θ_{t+1} = θ_t - η * m̂_t / (√(v̂_t) + ε)
作用:
-
兼具两者优点:像动量法一样加速在稳定方向的前进,像RMSProp一样自适应调整每个参数的学习率。
-
偏差校正:公式中的
m̂_t和v̂_t是对初始零值估计的偏差进行校正,使得初期更新更准确。 -
通常表现优异:在多种任务上都能实现快速且稳定的收敛。
总结 📝
本节课中我们一起学习了如何更高效地训练神经网络。
-
从批量到增量:我们了解到,为了处理大数据集,需要从批量更新转向增量更新。
-
SGD与小批量:随机梯度下降是增量更新的极端形式(批量大小为1),它计算快但方差高。小批量梯度下降是更实用的选择,在计算效率和稳定性之间取得了平衡。
-
优化算法的演进:基础的梯度下降存在收敛问题。动量法通过累积历史梯度来加速收敛并抑制振荡。RMSProp通过自适应调整每个参数的学习率来应对不同尺度的梯度。Adam综合了动量法和RMSProp的优点,成为目前广泛使用的强大优化器。
理解这些优化算法的原理,将帮助你在实际项目中根据具体问题选择合适的训练策略,从而更有效地训练你的深度学习模型。
9:神经网络训练技巧 🧠
在本节课中,我们将学习神经网络训练的最后一部分内容,包括损失函数的选择、批归一化、正则化以及Dropout等关键技术。这些技巧对于提升模型性能、加速收敛和防止过拟合至关重要。
损失函数与收敛性 🔍
上一节我们讨论了梯度下降和优化算法。本节中,我们来看看损失函数的选择如何影响网络的收敛。
我们通过最小化损失函数来训练网络。损失函数是网络参数的函数,它计算一批训练样本上的平均差异。我们真正希望最小化的是在整个数据分布上的期望差异。
我们通过梯度下降进行最小化,即沿着梯度的反方向迭代更新参数。理想情况下,损失函数的形状应能引导算法找到最优解。
以下是几种损失函数形状的对比:
-
左侧函数:形状崎岖不平,不利于优化。
-
中间函数:在远离最小值时梯度平缓,接近最小值时梯度陡峭,容易在最优解附近震荡。
-
右侧函数:远离最小值时梯度大,更新步长大;接近最小值时梯度变小,更新步长变谨慎。这是我们期望的二次型(碗状)形状。
我们主要使用两种损失函数:
-
L2损失(均方误差):用于回归任务,预测实数值输出。公式为:
L = (y_pred - y_true)^2 -
KL散度(交叉熵损失):用于分类任务,输出是概率。公式涉及对数概率。
一个关键问题是:在分类任务中,为什么使用KL散度而不是形状更好的L2损失?
虽然从网络最终输出(概率)的角度看,L2损失形状更优,但从网络倒数第二层线性输出 Z 的角度看,情况正好相反。KL散度作为 Z 的函数呈现出良好的凸性(碗状),而L2损失作为 Z 的函数则形状不佳。由于 Z 是权重和上一层输出的线性组合(Z = W * y_prev),损失函数关于 Z 的凸性意味着关于权重 W 也是凸的,这更有利于优化。因此,在分类任务中我们使用KL散度。
此外,无论使用L2还是KL损失,损失关于最终线性项 Z 的导数都是预测误差 (y_pred - y_true),这也是误差反向传播名称的由来。
批归一化(Batch Normalization)⚖️
我们使用小批量随机梯度下降来加速训练,其基本假设是每个小批量都能代表整体数据分布。但由于神经网络的非线性特性,即使输入相似,经过几层网络后,不同批次的激活值分布也可能产生很大差异(协变量偏移)。这导致在一个小批量上的优化不能很好地反映整体情况。
批归一化的核心思想是:在每一层的激活函数之前,对小批量数据的分布进行标准化,使其具有零均值和单位方差,然后通过可学习的参数进行缩放和偏移,将其调整到网络该层应有的分布。
具体操作如下:
-
计算小批量数据的均值
μ和方差σ^2。 -
对数据进行标准化:
u = (z - μ) / sqrt(σ^2 + ε),其中ε是为防止除零的小常数。 -
对标准化后的数据进行缩放和偏移:
z_hat = γ * u + β。其中γ和β是可学习的参数。
由于均值 μ 和方差 σ^2 依赖于整个小批量的数据,反向传播的计算会变得复杂。我们需要计算损失关于每个原始输入 z_i 的梯度,这涉及到 z_i 通过 μ 和 σ^2 对所有标准化输出 u_j 的影响。
批归一化能带来诸多好处:
-
允许使用更大的学习率。
-
减少对参数初始化的依赖。
-
在一定程度上起到正则化的效果。
重要注意事项:
-
批归一化依赖于小批量内的数据多样性。如果一个小批量内所有实例相同或极其相似,梯度可能会消失,阻碍训练。
-
在推理阶段,我们不再有小批量。通常使用训练阶段所有小批量统计的移动平均值作为推理时的
μ和σ^2。
正则化与权重衰减 🛡️
神经网络是通用函数逼近器,有能力完美拟合训练数据,导致过拟合。例如,一个复杂的网络可能学会一个在训练点上完全正确,但在其他区域极不合理的函数。
过拟合通常由具有过大权重的神经元导致,它们会产生非常陡峭的激活。为了防止过拟合,我们希望对模型的复杂度进行约束。
一种常见的方法是在损失函数中添加一个正则化项,惩罚大的权重。这被称为 L2正则化 或 权重衰减。
修改后的损失函数为:L_total = L_data + (λ/2) * ||W||^2
其中 L_data 是原始数据损失,λ 是控制正则化强度的超参数。
这轻微地改变了权重更新规则。新的梯度包含两部分:原始的反向传播梯度加上 λ * W。因此,权重更新公式变为:
W_new = W - η * (∇L_data + λW) = (1 - ηλ)W - η * ∇L_data
可以看到,在每次更新前,权重会先乘以一个小于1的因子 (1 - ηλ),从而实现“衰减”,然后再用梯度进行修正。
此外,网络深度本身也是一种隐式的正则化。对于相同数量的参数,更深的网络往往比更宽的网络学习到更平滑、泛化更好的函数。
Dropout:随机失活 🎲
在深度学习流行之前,集成学习(如Bagging)被证明能提升模型性能。其思想是:从训练数据中随机抽取多个子集,分别训练不同的模型,然后对它们的预测进行投票。
Dropout可以看作是在单个神经网络中实现Bagging的一种高效近似。在训练过程中,对于每个输入样本,我们以概率 p(例如0.5)随机“丢弃”(即暂时移除)网络中的每个神经元。这意味着:
-
每个输入样本看到的是不同的、更薄的子网络。
-
每次迭代时,被丢弃的神经元集合都可能不同。
Dropout为何有效?
-
集成学习的视角:一个具有
N个神经元的网络,通过Dropout可以产生2^N个可能的子网络。训练过程相当于同时在训练所有这些共享参数的子网络,并在推理时对它们的输出进行平均。 -
防止协同适应:没有Dropout时,某些神经元可能变得冗余,或者层与层之间可能形成固定的依赖路径。Dropout迫使每个神经元不能过分依赖于少数其他神经元,必须学习更鲁棒的特征。
实现细节:
-
训练时,对每个神经元应用一个伯努利掩码(0或1)。
-
反向传播时,只通过未被丢弃的神经元传递梯度。
-
推理时,我们需要近似所有可能子网络的平均输出。一个简单而有效的做法是:在训练时,将每个神经元的输出乘以
(1-p)(即保留概率);在推理时,则直接使用完整的网络。另一种等价做法是:训练时不做调整,推理时将每一层的权重乘以(1-p)。
Dropout是一种非常强大的正则化技术,但它可能与批归一化产生冲突,因此在实际中需要谨慎搭配使用。
其他训练技巧与总结 📝
除了上述主要技术,还有一些其他有用的训练启发式方法:
-
早停(Early Stopping):在训练过程中,定期在验证集上评估模型性能。当验证集性能不再提升甚至开始下降时,停止训练,以防止过拟合。
-
梯度裁剪(Gradient Clipping):当损失函数的梯度变得非常大时,单个批次或样本可能导致参数更新剧烈,破坏训练稳定性。梯度裁剪通过限制梯度向量的范数来解决这个问题。
-
数据增强(Data Augmentation):通过对训练数据应用随机变换(如旋转、裁剪、颜色抖动等)来人工增加数据量和多样性,这是提升模型泛化能力的有效手段。
本节课总结:
在本节课中,我们一起学习了神经网络训练的最后一系列关键技巧。我们探讨了损失函数(特别是KL散度)对优化过程凸性的影响;深入分析了批归一化如何通过标准化层间输出来稳定和加速训练;介绍了通过权重衰减进行正则化来防止过拟合;并详细解释了Dropout这一强大的随机正则化方法及其集成学习的本质。掌握这些技巧对于成功训练深度神经网络至关重要。下一节课,我们将开始学习卷积神经网络。
10:卷积神经网络(CNN)基础 🧠
在本节课中,我们将要学习如何构建一种能够识别模式(如唤醒词或图像中的花朵)的神经网络,并且这种识别不受模式在输入中具体位置的影响。我们将从简单的多层感知机(MLP)扫描方法开始,逐步推导出卷积神经网络(CNN)的核心思想。
概述:从位置敏感到位置不变
我们之前看到,多层感知机(MLP)是通用的函数逼近器,可以建模分类器和回归器。然而,传统的MLP对输入模式的位置非常敏感。例如,一个训练用于识别位于录音开头的单词“welcome”的MLP,可能无法识别位于录音中部的同一个单词,因为输入向量中激活的组件完全不同。
本节中,我们将探讨如何通过“扫描”输入并共享参数,来构建对模式位置不敏感的网络,这正是卷积神经网络的基础。
1. 问题:为什么需要位置不变性?
考虑两个任务:
-
语音唤醒词检测:判断一段录音中是否包含“welcome”这个词。
-
图像花朵检测:判断一张图片中是否包含一朵花。
如果使用一个大的MLP来处理整个输入(整段录音或整张图片),网络会学习到模式出现的精确位置。如果模式出现在训练时未见过的位置,网络很可能无法识别它,因为新的输入位于一个完全不同的子空间。
核心需求:我们需要的网络应该只关心模式是否存在,而不关心它具体出现在哪里。
2. 解决方案:扫描与共享参数
一个直观的解决方案是:我们不再用一个大网络处理整个输入,而是用一个较小的、能识别目标模式(如单词或花朵)的MLP去扫描输入。
以下是具体步骤:
-
定义一个能覆盖目标模式大小(例如,单词长度或花朵尺寸)的MLP。
-
将这个MLP像滑动窗口一样,在输入(时间轴或图像空间)上逐步移动。
-
在每个窗口位置,MLP都会输出一个值(例如,该位置存在目标的概率)。
-
最后,我们收集所有窗口位置的输出,并通过一个聚合操作(如取最大值
max或使用一个小的MLP)来得到最终判断。
关键洞察:
-
这个“扫描”过程等价于构建一个巨大的、共享参数的MLP。
-
所有扫描窗口使用的子MLP都是完全相同的,即它们共享同一套权重参数。
-
这种参数共享强制网络学习位置不变的特征。
公式/代码描述:
假设扫描窗口函数为 MLP_window,输入为 X,步长为 stride,则扫描过程可表示为:
outputs = []
for i in range(0, len(X) - window_size + 1, stride):
window = X[i:i+window_size]
outputs.append(MLP_window(window))
final_output = aggregate(outputs) # 例如 max(outputs)
3. 计算重排:从扫描MLP到特征图
上一节我们介绍了通过扫描MLP实现位置不变性的概念。本节中我们来看看如何重新组织计算流程,这能更清晰地揭示CNN的结构。
我们可以改变计算顺序,而不影响最终结果:
-
原始顺序:在每个位置,取出一个输入窗口,然后让这个窗口数据逐层通过整个MLP。
-
等价顺序:让第一层的所有神经元先独立工作,各自扫描整个输入,生成一张“特征图”(feature map)。这张图上的每个点对应输入中某个位置,该神经元对该位置的响应。
然后,第二层的神经元不再直接看原始输入,而是联合扫描第一层所有神经元生成的特征图。以此类推。
优势:
-
计算可视化:每一层都在生成对输入的一种新“解读”或特征图。
-
为参数分布奠定基础:这种视角让我们可以更容易地将识别大模式的责任分布到多个网络层中。
4. 参数分布:构建层次化特征
让第一层神经元直接识别整个花朵(或单词)可能负担过重且不灵活。更好的方法是构建一个层次化的特征检测系统。
分布式检测流程:
-
第一层:神经元只检测非常小的、基础的局部模式(例如,图像中的边角、纹理;语音中的音素片段)。它们扫描输入,生成多个基础特征图。
-
第二层:神经元接收来自第一层特征图的一个小窗口(例如2x2区域)。通过组合这些基础特征(如几个边角),它可以检测更复杂的模式(如一个花瓣的轮廓)。
-
更高层:重复此过程。每一层都通过组合下一层的特征,来检测更大、更复杂的模式(例如,由花瓣组成的花朵)。
核心概念:
-
感受野:一个神经元“看到”的原始输入区域的大小。随着层数加深,神经元的感受野会变得越来越大。
-
权重共享:在每一层内,用于扫描的神经元(滤波器)在不同位置使用的是相同的权重。这极大地减少了参数量。
公式/代码描述:
对于第 l 层的卷积操作(简化):
output_map[l][x] = activation( sum( weight[l] * output_map[l-1][x:x+k] ) + bias[l] )
其中 k 是滤波器大小,weight[l] 在该层所有位置共享。
5. 优势:为何使用这种分布式结构?
分布参数不仅是一种不同的组织方式,它带来了实实在在的好处:
-
层次化与泛化性:强制网络学习从简单到复杂的层次化特征表示,这与许多自然信号(如图像、语音)的结构相符,通常能带来更好的模型泛化能力。
-
参数效率:通过权重共享,参数量大幅减少。例如,一个直接处理8维输入窗口的层需要约
8*D*N1个参数(D为输入维度,N1为神经元数)。而将其分布到两个各看2维输入的层,仅需约2*D*N1 + 2*N1*N2个参数,当输入维度D较大时,节省非常显著。 -
计算效率:由于参数共享和滑动窗口的重叠,中间计算结果可以被大量重用,减少了总体计算量。
-
训练便利性:我们仍然只需要图像级别的标签(“有花”/“无花”),而不需要标注花朵的精确位置。通过反向传播和共享权重的梯度聚合,网络能自动学习在特征图中定位模式。
6. 术语与扩展概念
最后,我们来统一一下术语,并介绍两个关键概念:
-
滤波器:每一层中用于扫描的共享权重组,负责检测一种特定的模式。
-
特征图:一个滤波器在整个输入上扫描后产生的输出矩阵。
-
展平:在卷积层堆叠结束后,将最终的特征图拉平成一个长向量,以便输入给传统的全连接层或Softmax层进行分类。
过渡到下一步:上述结构已经具备了CNN的雏形。但为了获得更强的鲁棒性,我们还需要引入以下操作:
-
步长:滑动窗口移动的间隔。步长大于1可以降采样,减少计算量和特征图尺寸。
-
池化:通常跟在卷积层之后。在一个小区域(如2x2)内进行下采样,常用最大池化(
Max Pooling),即取该区域内的最大值。这能提供一定程度的平移不变性(即使目标轻微移动,仍能被捕获),并进一步减少数据维度。
公式/代码描述:
最大池化(2x2,步长2):
pooled_output[x, y] = max(input[2x:2x+2, 2y:2y+2])
总结
本节课中我们一起学习了卷积神经网络的核心思想及其演变过程:
-
我们从位置敏感性问题出发,提出了用MLP扫描输入并聚合结果的解决方案。
-
我们发现这等价于一个共享参数的大型网络。
-
通过重排计算顺序,我们得到了“特征图”的概念,每一层都在生成输入的一种新表示。
-
将识别大模式的任务分布到多个网络层,形成了层次化的特征检测结构,这大大提升了参数和计算效率,并鼓励学习更泛化的特征。
-
我们介绍了感受野、滤波器、步长和池化等关键术语与概念。
本质上,卷积神经网络是一个精心设计的、参数共享的MLP,它通过扫描和层次化处理,高效地实现了对平移不变模式的学习。在接下来的课程中,我们将深入探讨CNN的具体架构和训练细节。
11:卷积神经网络(CNN)📚
在本节课中,我们将学习卷积神经网络(CNN)的起源、核心概念和基本结构。我们将从生物视觉系统的工作原理出发,了解CNN如何受此启发而构建,并最终掌握其核心组件——卷积层和池化层的工作原理。
概述:从猫的视觉到人工神经网络 🐱➡️🧠
卷积神经网络并非凭空产生,其设计灵感源于对哺乳动物(特别是猫)视觉系统的科学研究。本节我们将回顾这段历史,理解视觉信息是如何在大脑中被分层处理的。
1959年,Hubel和Wiesel通过研究猫的视皮层(相当于人类的V1皮层),首次揭示了视觉处理的神经基础。他们发现,视皮层中的单个神经元并不响应视网膜上的所有光点,而只对特定小区域内的特定方向的光线模式(如竖线、横线)产生反应。每个神经元都有一个“感受野”。
他们进一步发现,视皮层中存在两级处理:简单细胞(S细胞) 负责检测特定方向的线性光模式;复杂细胞(C细胞) 则接收多个S细胞的输入,通过类似“最大响应”的机制来清理S细胞的输出,使其对噪声和微小位置变化更加鲁棒。这种S-C细胞层级结构,为后续的复杂模式识别奠定了基础。
从生物模型到计算模型:认知机与神经认知机 🧩
上一节我们介绍了生物视觉的基础。本节中我们来看看研究人员如何将这些原理转化为计算模型。
1980年,福岛邦彦(Kunihiko Fukushima)提出了神经认知机(Neocognitron),这是第一个受Hubel和Wiesel模型启发的计算视觉模型。该模型由多个块(Block) 堆叠而成,每个块内部包含两层:
-
S层:模拟简单细胞,使用可学习的参数来检测特定模式。
-
C层:模拟复杂细胞,执行固定的最大池化操作,对S层的输出进行下采样和鲁棒化处理。
神经认知机的关键创新在于权重共享:在同一个S层平面内,所有神经元使用相同的参数。这意味着无论模式出现在图像的哪个位置,只要该平面负责检测该模式,总会有某个神经元被激活,从而实现了位置不变性。这个模型通过无监督的赫布学习进行训练,能够自动从图像中学习到有意义的语义概念(如数字)。
引入监督学习:LeNet与CNN的诞生 🖼️➡️🔢
神经认知机展示了无监督学习视觉概念的潜力。然而,对于许多实际任务(如分类),我们需要明确的监督信号。本节我们来看看如何将监督学习引入这个框架。
答案很简单:在神经认知机的末端添加一个全连接层(如Softmax分类器),并使用带标签的数据进行训练。通过反向传播算法,整个网络(包括前面的S层参数)都可以被有监督地训练。这就是卷积神经网络(CNN) 的雏形。
Yann LeCun在20世纪80年代末至90年代初的工作(尤其是LeNet网络)是这一方向的里程碑。他成功地将CNN应用于美国邮政服务的手写数字识别(MNIST数据集),取得了巨大成功。在他的模型中:
-
S层被实现为卷积层,通过可学习的滤波器扫描输入。
-
C层被实现为池化层(通常是最大池化),执行下采样。
以下是卷积操作的核心公式。对于一个输入图像(或特征图)X和一个滤波器(核)W,在位置(i, j)的输出(在加偏置b和应用激活函数σ之前)为:
Z[i, j] = ∑_m ∑_n X[i+m, j+n] * W[m, n] + b
然后应用激活函数得到特征图:A[i, j] = σ(Z[i, j])。
在代码中,这通常通过高效的张量操作实现。一个简化的概念性伪代码如下:
# 假设 input 形状为 (C_in, H_in, W_in), filter 形状为 (C_out, C_in, K, K)
output = zeros(C_out, H_out, W_out)
for c_out in range(C_out): # 对于每个输出通道(每个滤波器)
for i in range(H_out):
for j in range(W_out):
# 提取输入的感受野区域
receptive_field = input[:, i:i+K, j:j+K] # 形状 (C_in, K, K)
# 计算点积并求和(加上偏置)
output[c_out, i, j] = sum(receptive_field * filter[c_out, :, :, :]) + bias[c_out]
# 随后对 output 应用激活函数,如 ReLU
CNN的核心组件详解 ⚙️
我们已经了解了CNN的历史和整体框架。现在,让我们深入探讨其两个核心组件的细节:卷积层和池化层。
卷积层(Convolutional Layer)
卷积层是CNN中可学习的部分,负责提取特征。
以下是卷积层的关键特性:
-
滤波器(核):每个滤波器是一个小的权重矩阵(如3x3, 5x5),用于检测一种特定的局部模式(如边缘、纹理)。
-
通道:输入可以有多个通道(如RGB图像的3个通道)。每个滤波器也会具有相应的深度,同时处理所有输入通道的信息。
-
输出特征图:每个滤波器扫描整个输入,生成一张输出特征图。输出特征图的数量等于滤波器的数量。
-
步长(Stride):滤波器每次移动的像素数。步长为1是常见选择,步长大于1会减少输出尺寸。
-
填充(Padding):在输入周围添加零值像素,通常用于控制输出特征图的大小(例如,保持与输入尺寸相同)。
池化层(Pooling Layer)
池化层通常跟在卷积层之后,用于降低特征图的空间尺寸,增强特征的鲁棒性。
以下是池化层的关键特性:
-
操作:最常见的是最大池化(Max Pooling),它在一个小窗口(如2x2)内取最大值。也有平均池化等其他形式。
-
作用:
-
下采样:减少数据量和计算复杂度。
-
平移不变性:使特征对微小的位置变化不敏感。
-
扩大感受野:使后续层能够基于更广阔的区域进行判断。
-
-
实现细节:在反向传播时,需要记录最大值所在的位置,以便将梯度正确地传回。
特征图尺寸变换:下采样与上采样 🔽🔼
在网络的前向传播过程中,特征图的尺寸会发生变化。理解这些操作对设计网络结构至关重要。
下采样(Downsampling)
下采样用于减少特征图的空间尺寸。
以下是实现下采样的主要方式:
-
池化操作:最大池化或平均池化在操作时,如果步长大于1,会直接实现下采样。
-
带步长的卷积:卷积层也可以设置步长大于1,在提取特征的同时进行下采样。
-
简单丢弃:理论上可以直接每隔S个像素保留一个,但实践中通常与卷积或池化合并。
上采样(Upsampling)
上采样用于增加特征图的空间尺寸,常见于图像分割、生成等任务。
以下是上采样的常见方法:
-
最近邻插值:复制相邻像素的值。
-
双线性/双三次插值:基于周围像素进行加权平均。
-
转置卷积(Transposed Convolution):一种可学习的上采样方法,通过学习的滤波器来“填充”扩大的区域。
-
反池化(Unpooling):与最大池化对应,将值放回池化前记录的最大值位置,其他位置填零。
一个典型的上采样(如最近邻插值,因子为2)的简单伪代码示例如下:
def upsample_nearest(input, scale_factor=2):
H, W = input.shape
new_H, new_W = H * scale_factor, W * scale_factor
output = np.zeros((new_H, new_W))
for i in range(new_H):
for j in range(new_W):
# 找到在原始输入中对应的位置
src_i = i // scale_factor
src_j = j // scale_factor
output[i, j] = input[src_i, src_j]
return output
重要关系:为了在多次下采样后不丢失过多信息,网络通常会增加通道数。这样,尽管空间尺寸(高度、宽度)减小,但信息容量(通过更多的特征图)得以保持。当需要上采样恢复细节时,这些丰富的通道信息可以提供必要的上下文。
构建一个典型的CNN分类网络 🏗️
现在,让我们把所有这些组件组合起来,看一个用于图像分类的典型CNN结构示例。
一个经典的顺序可能是:
-
输入:RGB图像(3通道)。
-
卷积块1:
-
卷积层(如:32个3x3滤波器,填充1,步长1) + ReLU激活。
-
池化层(如:2x2最大池化,步长2)。
-
-
卷积块2:
-
卷积层(如:64个3x3滤波器,填充1,步长1) + ReLU激活。
-
池化层(如:2x2最大池化,步长2)。
-
-
卷积块N:可以重复更多次,每次通常增加滤波器数量。
-
展平层:将最后的二维特征图转换为一维向量。
-
全连接层:一个或多个传统的神经网络层,用于最终分类。
-
输出层:Softmax层,输出每个类别的概率。
在这个结构中,随着网络加深,特征图的空间尺寸逐渐减小(由于池化),但通道数逐渐增加。最后的全连接层整合所有高级特征,做出决策。
总结 📝
本节课中我们一起学习了卷积神经网络(CNN)的完整故事:
-
生物起源:CNN的设计灵感来源于Hubel和Wiesel对猫视觉皮层的研究,特别是简单细胞(S细胞)和复杂细胞(C细胞)的分层处理机制。
-
模型演化:福岛邦彦的神经认知机首次将这些原理计算化,通过无监督学习实现模式识别。LeCun等人通过引入监督学习(在末端添加分类器),创造了现代CNN的雏形。
-
核心组件:
-
卷积层:使用可学习的滤波器扫描输入,提取局部特征,通过权重共享实现平移不变性。
-
池化层(尤其是最大池化):对特征图进行下采样,增强鲁棒性,扩大感受野。
-
-
尺寸管理:通过下采样(池化、带步长卷积)减少计算量,通过增加通道数来补偿信息损失;上采样操作则用于需要恢复空间分辨率的任务。
-
网络结构:典型的CNN由交替的卷积层和池化层堆叠而成,最后连接全连接层进行分类。
CNN成功地将空间结构的理解引入了神经网络,使其成为处理图像、视频甚至某些序列数据的强大工具。在接下来的课程中,我们将深入探讨如何训练这些网络,即CNN中的反向传播算法。
12:卷积神经网络(CNN)的训练与反向传播 🧠
在本节课中,我们将学习如何训练卷积神经网络(CNN),重点在于理解其独特的反向传播过程。我们将回顾CNN的基本结构,并详细探讨如何计算卷积层和池化层的梯度。
概述
卷积神经网络通过扫描输入并组合结果来执行模式分类任务,这等效于使用共享参数的神经元进行分层扫描。网络结构包含卷积层、池化层以及最终的多层感知机(MLP)。训练CNN的目标是学习这些层的参数(即滤波器权重和MLP参数),以最小化预测输出与真实标签之间的损失。
CNN结构回顾
上一节我们介绍了CNN的基本组件。本节中,我们来看看其具体结构。
一个典型的CNN由以下部分组成:
-
卷积层:包含两个阶段。首先,使用滤波器在所有输入通道上进行卷积,生成仿射图。然后,对仿射图逐点应用激活函数,生成激活图。
-
池化层:间歇性地跟随卷积层,通过对局部区域(如最大值或平均值)进行下采样来减少特征图尺寸并增加一定程度的平移不变性。
-
重采样层:用于按比例增大或减小特征图尺寸。下采样通过删除行和列实现,上采样通过插入零行和零列实现。
-
MLP分类器:将最后一个卷积/池化层输出的扁平化向量作为输入,进行最终分类。
在训练时,所有卷积层的滤波器和MLP的权重都需要通过反向传播和梯度下降来学习。
反向传播原理
训练神经网络,包括CNN,核心在于通过反向传播计算损失函数相对于所有网络参数的梯度。
对于单个训练样本,其流程如下:
-
前向传播:输入通过网络,计算得到输出。
-
计算损失:计算网络输出与真实标签之间的差异(损失)。
-
反向传播:从输出层开始,向后逐层计算损失相对于每一层输入和参数的梯度。
总体损失是训练集上所有样本损失的平均值。因此,任何参数的梯度也是所有样本对该参数梯度的平均值。
对于CNN,反向传播在MLP部分是常规操作。挑战在于如何通过卷积层和池化层进行反向传播。
卷积层的反向传播
现在我们已经理解了反向传播的整体目标,本节中我们来看看卷积层具体的梯度计算规则。
假设我们已经通过反向传播得到了损失 L 相对于第 l 层输出激活图 Y^l 的梯度 dL/dY^l。我们需要计算:
-
损失相对于第
l层仿射图Z^l的梯度dL/dZ^l。 -
损失相对于第
l层滤波器权重W^l的梯度dL/dW^l。 -
损失相对于前一层(第
l-1层)输出Y^{l-1}的梯度dL/dY^{l-1}。
1. 从激活梯度到仿射图梯度
由于 Y^l = f(Z^l),其中 f 是逐点应用的激活函数(如ReLU),因此梯度计算很简单:
公式:
dL/dZ^l = dL/dY^l * f'(Z^l)
这里 * 表示逐元素相乘。
2. 计算对前一层输入的梯度
这是卷积层反向传播的核心。在前向传播中,每个输出仿射图元素 Z^l(x, y) 都是由前一层的一片区域与滤波器进行卷积(点积)得到的。因此,在反向传播时,损失相对于前一层某个输入 Y^{l-1}(a, b) 的梯度,需要累加所有受到该输入影响的输出元素 Z^l 的贡献。
计算规则可以表述为一次卷积操作:
-
将第
l层的滤波器在空间维度上进行翻转(上下翻转,左右翻转)。 -
用这个翻转后的滤波器,对第
l层的梯度图dL/dZ^l进行卷积(需进行适当的零填充,以使输出尺寸与Y^{l-1}匹配)。 -
由于每个输入通道被所有输出通道的滤波器使用,因此需要对所有输出通道的上述卷积结果进行求和,才能得到对单个输入通道的最终梯度。
核心概念:对输入 Y^{l-1} 的梯度计算,等价于用翻转后的滤波器对 dL/dZ^l 进行卷积。
3. 计算对滤波器权重的梯度
类似地,损失相对于滤波器某个权重 W^l(i, j) 的梯度,需要累加所有使用该权重的输出位置 Z^l(x, y) 的贡献。
计算规则也可以表述为一次卷积操作:
-
将第
l-1层的输入激活图Y^{l-1}作为“输入”。 -
将第
l层的梯度图dL/dZ^l作为“滤波器”。 -
对这两者进行卷积(无需翻转),得到的结果就是损失相对于整个滤波器
W^l的梯度。
核心概念:对滤波器权重 W^l 的梯度计算,等价于用 Y^{l-1} 对 dL/dZ^l 进行卷积。
池化层的反向传播
理解了卷积层的反向传播后,本节中我们来看看池化层的梯度如何计算。池化层没有可学习的参数,其作用是将梯度从输出分配回输入。
最大池化(Max Pooling)
在前向传播中,最大池化从每个局部窗口中选择最大值作为输出。在反向传播时:
-
梯度只会传递给前向传播中被选为最大值的那一个输入位置。
-
其他位置的梯度为零。
-
如果一个输入元素在多个重叠的池化窗口中都曾是最大值,那么它会接收到来自所有这些窗口的梯度之和。
伪代码逻辑:
# 前向传播时记录每个输出位置对应的最大值的索引 (max_index)
# backward_pass:
dY_prev[...] = 0 # 初始化输入梯度为0
for each output position (x, y):
dY_prev[max_index[x, y]] += dL/dY_pool[x, y] # 将梯度累加到原最大值位置
平均池化(Average Pooling)
在前向传播中,平均池化计算每个局部窗口的平均值作为输出。在反向传播时:
-
梯度被均匀地分配回该窗口内的所有输入位置。
-
每个输入位置接收到的梯度是输出梯度除以窗口大小(例如,2x2窗口则除以4)。
伪代码逻辑:
# backward_pass:
dY_prev[...] = 0 # 初始化输入梯度为0
pool_size = k * k # 例如 k=2
for each output position (x, y):
for each element in the corresponding input window (i, j):
dY_prev[i, j] += (1 / pool_size) * dL/dY_pool[x, y] # 均匀分配梯度
总结
本节课中我们一起学习了卷积神经网络(CNN)训练的核心——反向传播。我们回顾了CNN的组成结构,并深入探讨了如何计算卷积层和池化层的梯度。
关键要点如下:
-
卷积层反向传播:可以巧妙地用卷积运算来实现。对输入的梯度计算涉及与翻转滤波器的卷积,而对滤波器权重的梯度计算则涉及与输入图的卷积。
-
池化层反向传播:根据池化类型分配梯度。最大池化将梯度路由回最大值所在位置;平均池化将梯度均匀分配回池化窗口内的所有位置。
-
训练流程:通过前向传播计算输出和损失,然后通过上述规则反向传播梯度,最终使用梯度下降法更新所有参数(卷积滤波器和MLP权重)。
通过掌握这些规则,我们便能够从零开始理解并实现CNN的训练过程。下一节课我们将继续探讨重采样(上/下采样)操作的反向传播。
13:卷积神经网络(CNN)进阶与架构演变 🧠
在本节课中,我们将深入学习卷积神经网络(CNN)的剩余核心概念,包括完整的反向传播规则、处理上/下采样的方法、如何引入其他变换不变性,以及一些经典的CNN架构演变。我们将确保内容简单直白,让初学者能够跟上。
1. CNN反向传播快速回顾 🔄
上一节我们介绍了CNN的基本结构和前向传播。本节中,我们来看看如何通过反向传播来训练CNN。
对于每个训练数据:
-
将数据实例通过模型前向传播,得到输出。
-
计算模型输出与期望输出之间的差异(损失)。
-
使用标准的反向传播规则,将梯度从输出层向后传播,直到CNN之后的第一层MLP。
-
将该MLP输入处的梯度“折叠”回来,得到CNN最后一层所有通道的梯度。
-
从此处开始,使用我们上一堂课见过的反向传播规则,将导数向后传播。这些规则必须考虑卷积层中的共享计算和池化层的特殊计算。
在每个层,我们需要计算两种导数:
-
对于卷积层:给定输出激活图
y^L的导数,我们需要计算其对应的仿射图Z^L的导数,然后再计算对前一层输出y^(L-1)和滤波器权重W^L的导数。 -
对于池化层:给定输出
y^L的导数,我们需要计算对输入y^(L-1)的导数。
2. 卷积层的反向传播规则 🧮
首先,我们来看如何从激活图的导数得到仿射图的导数。这很简单,因为激活是通过逐点应用激活函数 f 得到的。
公式:
y^L[m, x, y] = f(Z^L[m, x, y])
因此,根据链式法则,对仿射项 Z^L[m, x, y] 的导数就是对激活 y^L[m, x, y] 的导数乘以激活函数在该 Z 值处的导数。
代码概念:
# dL_dZ 是损失L对仿射图Z的导数
# dL_dY 是损失L对激活图Y的导数
# f_prime 是激活函数的导数
dL_dZ = dL_dY * f_prime(Z)
接下来,我们需要计算对输入 y^(L-1) 和滤波器 W^L 的导数。
-
对输入
y^(L-1)的导数:将第m个输入通道对应的所有滤波器的第m个通道进行转置(即左右、上下翻转),然后与零填充后的输出仿射图导数进行卷积。 -
对滤波器
W^L的导数:为了计算第n个滤波器的导数,我们将第n个输出仿射通道的导数图与所有输入通道进行卷积。
3. 池化层的反向传播规则 📉
我们考虑两种池化:最大池化和平均池化。
-
最大池化:在前向传播中,我们取一个窗口内的最大值。在反向传播时,只有那个最大值元素对输出有贡献。因此,梯度只被复制回前向传播中被选中的那个最大值位置,窗口内其他位置的梯度为零。
-
平均池化:在前向传播中,我们取窗口内所有值的平均值。在反向传播时,梯度被均匀地分配回窗口内的所有元素。如果窗口大小为4,那么每个位置将得到输出梯度四分之一的贡献。需要注意的是,由于窗口可能重叠,同一个输入位置可能从多个窗口接收梯度,因此需要进行累加(
+=)。
平均池化的反向传播操作可以看作是一种卷积操作。
4. 处理上采样与下采样 🔼🔽
上采样和下采样层会改变特征图的大小。我们之前提到,上采样后通常要接一个卷积,下采样前通常有卷积或池化。
下采样的反向传播:
-
前向传播:从一个大图中,每隔一定步长(stride)取样,忽略其他位置(可视为置零)。
-
反向传播:我们需要一个与输入同样大小的导数图。首先将其全部置零,然后将输出导数图的值复制到输入图中对应被取样的位置。这本质上是一个上采样操作。
上采样的反向传播:
-
前向传播:通过在特征图中插入零值行和列来增大尺寸。
-
反向传播:只有那些来自原始输入的值(非插入的零)才有梯度。因此,我们丢弃那些对应于插入零位置的梯度,只保留原始位置的梯度。这本质上是一个下采样操作。
重要提示:虽然数学上互为逆操作,但由于边界处理方式不同,在代码实现中不能简单地用上采样代码去实现下采样的反向传播,反之亦然。
5. 处理步长大于1的卷积和池化 🏃♂️
直接为步长(stride)大于1的卷积推导反向传播规则会很复杂。一个更简单的方法是将其分解:
-
卷积 stride > 1:可以视为一个 stride=1的卷积,后面接一个 下采样层。然后分别对这两个简单操作进行反向传播。
-
卷积 fractional stride (stride < 1):可以视为一个 上采样层,后面接一个 stride=1的卷积。
-
池化 stride > 1:同样可以分解为 stride=1的池化,后面接一个 下采样层。对于最大池化,由于其本身会记录最大值位置,实现起来仍然简单;对于平均池化,分解则使逻辑更清晰。
从代码角度思考通常更简单:只需反转前向传播循环中的操作顺序和步进逻辑即可。
6. 超越平移不变性:其他变换 ✨
CNN天然具有平移不变性。但我们可能还希望它对旋转、缩放等变换具有不变性。理论上,可以通过对每个滤波器枚举所有希望不变的变换(如旋转45度、翻转),生成相应的变换后滤波器,然后用它们一起扫描输入。
问题:
-
这会导致输出通道数急剧增加(原始滤波器数 × 变换数)。
-
反向传播时,所有变换后滤波器的梯度必须经过相应的逆变换后,再聚合到原始滤波器上。
-
计算量巨大。
因此,在实践中,我们很少显式构建这种变换不变的CNN。更常用的方法是数据增强:在训练时,对输入数据随机进行旋转、缩放、裁剪等变换,从而让网络自己从数据中学习到这些不变性。
7. 目标定位与检测 📍
基础的CNN只能判断图像中是否有某类物体。如果我们还想知道物体的位置,该怎么做?
方法:在CNN的末端,除了用于分类的全连接层,我们并行地添加一个回归层。
-
输入:CNN提取的最终特征(展平后)。
-
输出:一个边界框的坐标,例如
(x1, y1, x2, y2, x3, y3, x4, y4)代表框的四个角点。 -
训练:需要带有边界框标注的数据。总损失是分类损失和边界框回归损失的加权和。
-
应用:不仅可以定位物体,还可以用于姿态估计(预测人体关节位置)。
8. 经典架构:深度可分离卷积 🧩
标准的卷积操作计算成本高。为了提升效率,提出了深度可分离卷积,它分为两步:
-
深度卷积:使用一个滤波器(其通道数与输入相同),分别与每个输入通道进行卷积,产生与输入通道数相等的输出通道。这一步是通道分离的。
-
逐点卷积:使用多个 1x1 的卷积核,对上一步得到的多通道特征图进行组合,得到最终输出通道。这一步负责通道混合。
优势:
-
大幅减少参数和计算量。假设输入有
C_in个通道,输出需要C_out个通道。-
标准卷积:需要
C_out个C_in通道的滤波器,计算复杂度约为C_in * C_out * K^2(K为核大小)。 -
深度可分离卷积:计算复杂度约为
C_in * K^2 + C_in * C_out。
-
-
在移动端和资源受限场景中非常有用。
权衡:由于减少了参数和计算,在数据充足的情况下,其表示能力可能略低于标准卷积。
9. CNN学到了什么? 🎨
通过可视化CNN的滤波器,我们可以洞察其工作原理:
-
底层神经元:学习到类似Gabor滤波器的简单边缘、纹理模式,与哺乳动物视觉皮层V1区的神经元响应惊人地相似。
-
高层神经元:响应越来越复杂的模式。当输入是人脸时,高层神经元可能对应眼睛、鼻子等部件;当输入是车辆时,则对应车轮、车灯等。最高层的神经元可能响应整个物体(如一张完整的脸、一辆完整的车)。
-
这证明了CNN确实在学习一种层次化的、由简到繁的特征表示。
10. 训练技巧与成功故事 🏆
训练深度CNN需要一些技巧:
-
优化器:使用Adam、带动量的SGD等高级优化算法。
-
正则化:使用批归一化、Dropout等防止过拟合。
-
数据增强:如前所述,通过对训练图像进行随机变换来扩充数据集,提升模型泛化能力。
CNN发展史上的里程碑:
-
LeNet-5 (1998):早期成功的CNN,用于手写数字识别。
-
AlexNet (2012):在ImageNet大赛上取得突破性成功,将Top-5错误率从25%以上降至16.4%,开启了深度学习新时代。它使用了ReLU激活函数和多GPU训练。
-
VGGNet (2014):通过堆叠多个3x3的小卷积核来构建深度网络,结构简洁有效。
-
GoogLeNet / Inception (2014):提出了Inception模块,在增加网络深度的同时巧妙控制计算量。
-
ResNet (2015):引入了残差连接,解决了极深网络中的梯度消失/爆炸问题,使得训练数百甚至上千层的网络成为可能,并将ImageNet错误率降至3.57%。
-
DenseNet等:后续出现了更多高效架构。
如今,CNN及其变体已广泛应用于计算机视觉、语音识别、自然语言处理等诸多领域。
总结 📚
本节课中,我们一起深入学习了卷积神经网络(CNN)的多个进阶主题:
-
我们回顾并完善了CNN中卷积层和池化层的反向传播规则。
-
我们学习了如何处理上采样、下采样以及步长大于1的卷积,理解了将其分解为基本操作的思想。
-
我们探讨了超越平移不变性的其他变换不变性,并理解了数据增强是更实用的方法。
-
我们了解了如何使用CNN进行目标定位,即通过添加回归层来预测边界框。
-
我们认识了深度可分离卷积这一高效架构,它通过分离空间滤波和通道组合来大幅降低计算成本。
-
我们通过可视化看到了CNN从简单边缘到复杂物体的层次化学习过程。
-
最后,我们回顾了CNN发展史上的关键架构和成功故事,从LeNet到ResNet,见证了其强大的能力和演变历程。
CNN作为深度学习的基础模型之一,其核心思想——共享权重的局部连接和层次化特征提取——将继续影响着众多人工智能应用的发展。
14:循环神经网络(RNN)📈
在本节课中,我们将学习如何利用神经网络来处理和分析序列数据。我们将从有限响应系统的问题出发,逐步引入能够记忆“无限过去”的循环神经网络(RNN),并探讨其基本结构、工作原理以及训练方法。
序列建模的必要性
在许多实际应用中,我们需要考虑一系列输入来产生一个或多个输出。例如:
-
语音识别:需要分析整段语音录音才能判断说话内容。
-
情感分析:需要理解整个句子的上下文才能判断其情感倾向。
-
机器翻译:需要读完整个英文句子才能输出对应的法语句子。
-
股票市场预测:需要分析过去一段时间内的股价序列,才能对未来做出投资决策。
这些都属于分类和预测问题,其共同点是需要考虑一个输入向量序列来产生输出。显然,我们可以用神经网络来完成这些任务。
从有限响应到无限响应
上一节我们介绍了卷积神经网络(CNN)在处理序列时的应用。本节我们来看看它的局限性。
卷积神经网络(CNN)的局限性
对于时间序列问题,一个直观的想法是使用一维卷积神经网络(1D-CNN)。网络会查看当前输入以及过去几天的输入(一个固定大小的窗口),然后做出预测。
然而,这种模型是一个有限响应系统。发生在时间 t 的事件只会影响未来 n 天内的输出(n 是网络查看的窗口大小)。一旦窗口滑过该事件,其影响就消失了。
问题:市场趋势往往包含更长的模式(如周模式、月模式、年模式)。为了考虑所有这些长期历史趋势,窗口大小必须不断增加。这会导致模型参数数量急剧增长,且难以捕捉真正长期的依赖关系。
我们真正需要的是一个无限响应系统,能够考虑“无限过去”的信息来做预测。
引入循环:从NARX网络到简单循环网络
为了构建无限响应系统,最直接的想法是将过去的输出反馈给输入。
NARX网络(非线性自回归外生输入网络)
在这种模型中,任何时刻 t 的输出 y(t) 不仅是当前输入 x(t) 的函数,也是前一个时刻输出 y(t-1) 的函数。
公式:
y(t) = f( x(t), y(t-1) )
特点:
-
一个在时间
0的输入会影响所有未来的输出,实现了无限响应。 -
然而,记忆完全存储在外部的前一个输出值中,而不是在网络内部。
更多推荐


所有评论(0)