1.5 微积分与凸优化 导数、梯度与凸函数
1.从导数开始热身
上一章我们聊过梯度下降那个蒙眼下山的比喻,那会儿我留了个伏笔,说梯度的底子其实是导数。这一章我们把微积分和凸优化这块数学底子补一补,免得后面看到代码里的反向传播一头雾水,也不至于调起参数来还搞不清自己到底在调什么。
先从最基础的单变量导数说起。给你一个函数 ,它描述了输入 和输出 之间的对应关系。导数记作 ,也可以写成 ,它衡量的是在某一点上, 稍微动那么一点点,函数值 会跟着变多少。
几何上看,导数其实就是切线的斜率。想象函数图像上某一点,贴着这一点画一条最贴合的直线,这条直线的陡峭程度就是导数。导数为正,说明函数在这里是往上走的,导数为负那就是往下走,导数接近零,说明这里差不多平了,多半是个极值点,也就是局部最高或者最低的地方。我之前翻过一本讲微积分的科普书,它把导数比作一辆车某一瞬间的速度表读数,瞬时速度只关心眼下的快慢,不关心整段路怎么走,我觉得这个比方格外好懂。
我们拿一个最朴素的例子练手。设 ,这是一条开口朝上的抛物线,谷底在原点。它的导数是 。在 这一点上,导数 ,是正数,意思是从这里往右走函数值会涨,往左走会跌。你想下山的话显然得往左走,也就是朝着导数的反方向走。这条思路其实已经暗含了后面梯度下降的全部精髓。
2.多个变量怎么办:偏导数
现实里的函数基本不会只有一个输入。还记得上一章那个预测学生成绩的例子吧,输入至少有复习时长、作业完成度、上课认真程度好几个,输出还是同一个分数。这时候函数就长成 这样,括号里有好几个自变量,下标 、、 用来给它们编号。
那导数怎么求呢,办法特别接地气,叫偏导数。你想看哪个变量的影响,就只对它求导,剩下的变量一律当成常数摁住不动。记号写作 ,那个弯弯的 是偏导数专用的符号,专门跟普通导数的 区分开。打个比方,这有点像我们观察一支篮球队里某一位球员的表现,先把其他队友的状态暂时当成固定的,只盯他一个人怎么跑位、怎么出手,最后再把每个人单独看一遍综合起来。
举个数字例子大家就明白了。设 。对 求偏导的时候,把 当成普通数字,于是 。对 求偏导的时候, 这一项跟 没关系,求下来是零,剩下 。你看,偏导数本质上就是一次只让一个变量动,看看函数值会怎么响应。
3.梯度:把偏导数打包成向量
光会求偏导数还不够,训练神经网络的时候参数动辄几百万个,总不能一个一个盯着看。于是我们把函数对每一个变量的偏导数统统收集起来,按顺序排成一个向量,这个向量就叫梯度,记作 ,那个倒三角 叫nabla算子,专门用来表示求梯度这个操作。
对前面那个 ,它的梯度就是 ,一个有两个分量的向量。
梯度有一个特别重要的性质,这里我不妨多啰嗦两句,因为整本书都得靠它:在当前点,梯度指向函数值上升最快的方向。上一章我们说梯度下降就是顺着梯度的反方向走,根子就扎在这条性质上。梯度告诉你哪边最陡地往上爬,你掉个头往反方向走,那自然就是最快地往下滑。
为什么梯度是最速上升方向:方向导数 + Cauchy–Schwarz。 这条性质值得推一遍,不然总像悬在半空。我们先定义方向导数:在点 处,沿着某个单位方向 ()走一小步,函数值的变化率叫方向导数,记作 。用链式法则可以证明,方向导数正好等于梯度和这个方向的内积:
现在的问题变成:在所有可能的单位方向 里,哪个方向能让 最大?用 Cauchy–Schwarz 不等式(两个向量的内积不超过它们长度之积):
等号成立当且仅当 和 同向。也就是说,让方向导数最大的方向 ,就是 ——梯度的方向。这就严格证明了"梯度指向函数值上升最快的方向"。反过来,负梯度 让方向导数最小(最负),所以是下降最快的方向,这就是梯度下降的数学根基。光靠直觉记"梯度是上山最快的路",不如把这几行推导看明白,因为它顺便告诉你最快下降的步长跟梯度长度有关——这影响了后面学习率怎么选。
后面大家写PyTorch代码的时候,框架干的一大活儿就是帮你自动把这个梯度给算出来,叫自动求导,省得手推。但原理你还得懂,不然训练出问题的时候,都不知道问题出在哪里。
4.雅可比和海森:往高维再走一步
函数这东西种类繁多。有的函数吃进去一个向量,吐出来一个数,比如损失函数,这种叫标量函数。有的函数吃进去一个向量,吐出来也是一个向量,比如神经网络某一层的输出是好几个数,这种叫向量函数。
向量函数对输入求导,结果就是一个矩阵,叫雅可比矩阵(Jacobian matrix,雅可比是位德国数学家的名字)。雅可比矩阵里的每一个位置放的是输出向量的某一分量对输入向量的某一分量的偏导数,相当于把所有偏导数按行和列排成一张表。把它想成向量版本的一阶导数就行,它描述了输入稍微变一点点的时候,输出的每一个分量各自会怎么响应。
那如果对标量函数求二阶导呢。二阶导数描述的是导数本身的变化率,换句话说就是曲面弯曲的程度。把所有二阶偏导数也排成一个矩阵,就叫海森矩阵(Hessian matrix,海森是另一位德国物理学家的名字)。海森矩阵告诉你这个函数曲面在某点上是凹的还是凸的、弯曲得有多厉害。
这两个矩阵在后面推导优化算法的时候会反复出场,比如想用比普通梯度下降更聪明的二阶方法时,就得算海森。不过入门阶段大家先混个眼熟,知道雅可比代表向量函数的一阶导数、海森代表标量函数的二阶导数就够了,剩下的我们用到的时候再细说。
5.泰勒展开:用多项式去贴近一个函数
接下来这一位也很重要,叫泰勒展开(Taylor expansion,泰勒是英国数学家)。它干的事说穿了特别朴素:任何一个看起来弯弯绕绕、画图都不好画的函数,在局部一小段里,都可以用一个多项式去近似它,而且项数越多贴得越准。
最常用的是一阶泰勒展开。函数 在某一点 附近,可以近似成:
这个式子右边就是一条直线的方程,斜率是 ,经过点 。所以一阶泰勒展开说白了就是切线近似,在 这一点附近拿切线去代替原来的曲线。这一点特别关键,划重点:梯度下降之所以有效,本质就是因为它顺着这条切线,也就是一阶近似,往低处迈步。它压根没去管曲面到底怎么弯,只看了眼前这一刀切的走势,所以它简单、好算,但有时候也难免被曲面骗到。
二阶泰勒展开会再多加一项和海森矩阵有关的东西,把曲面的弯曲也考虑进去,于是近似得更准了,代价是计算量大得吓人。所以平时训练网络,绝大多数时候大家还是老老实实用一阶的梯度下降,毕竟算得快才是王道。
6.凸函数:为什么线性模型那么好训
铺垫了这么多,终于可以聊聊这一章真正的主角了,凸函数。
先说凸集。一个集合如果是凸的,意思是你在这个集合里随便挑两个点,把它们用直线连起来,这条线段全程都还在集合里头,不跑出去。一个碗的内部就是凸集,一个甜甜圈就不是。
再说凸函数。凸函数的图像长得像一个朝上的碗,U字形那种。严格的定义是:函数图像上任意挑两点连成一条直线,这条线全程都在函数图像的上方或者刚好贴着。等价的另一种说法是,这个函数上随便挑两点 和 ,它们满足:
其中 是 到 之间的一个数。这个式子的意思就是, 和 的加权平均点上的函数值,不超过函数值的加权平均,画出来正好就是碗壁向上拱的状态。
凸函数最让人省心的地方在于,它的任何局部最小值就是全局最小值。换句话说你在碗壁上随便放一个球,它一路滚到底就是那个最低点(碗底),半道上没有别的坑能把它截住。严格凸的碗才有唯一的最低点,普通凸函数底部也可能是一小段平地。这个性质对训练模型来说简直是泼天富贵,因为它意味着梯度下降几乎一定能漂亮地收敛到最优解,根本不用愁会卡在半道上。
为什么凸函数的局部最小就是全局最小。 我们用反证法把这条性质推一下。设 是一个局部最小值(它附近一小片都比它大或相等),但假设它不是全局最小——也就是说存在另一点 ,满足 。现在我们取 和 的一个凸组合 ,其中 是一个很接近 的小正数,让 落在 的邻域内。由凸性:
因为 ,右边的 ,于是 。但我们说 落在 的邻域里,而 是局部最小,邻域内不该有比它更小的点——矛盾!这个矛盾说明假设错, 必须是全局最小。证毕。这条性质是线性回归、逻辑回归、SVM 这些模型"理论干净、好训"的根,它们的目标函数都是凸的。
举个大家最熟的例子,线性回归的平方损失:
就是把每一个样本的预测值 减去真实值 的平方,再全部加起来,下标 用来遍历所有样本。这个损失对参数来说就是一个漂亮的凸函数,所以线性回归才那么好训,闭着眼睛跑梯度下降都能收敛到解析解附近。这也是为什么早期的机器学习模型(线性回归、逻辑回归、支持向量机这一票)理论上都很干净,属于那种不用怎么折腾就能上分的稳当选择,大家调起来也舒服。
7.非凸的世界:神经网络为什么这么难训
那么问题来了,神经网络它偏不。它的损失曲面是高度非凸的,到处坑坑洼洼,像一张被随手揉皱了的山地图。损失曲面上满是局部最小值和鞍点。
局部最小值就是那种小坑,球滚进去之后四面都是上坡,梯度为零,梯度下降到这里就以为到谷底了,其实离真正的全局最低还差着十万八千里。鞍点更阴险,在这种点上,有的方向看是最低、有的方向看是最高,形状像马鞍(saddle point,英语里就是马鞍的意思),梯度同样是零,但它根本不是最低点,球停在这里纯属被卡住动弹不得。
听起来挺让人头疼的,但是你还真别说,实际训练神经网络的时候,梯度下降照样能找到相当不错的解。这里头有几条直觉可以解释。首先,高维空间里的局部最小其实没那么坑,因为维度一高,所有方向同时都朝上的概率极低,所以大量看起来像局部最小的点,其实损失值和全局最低差不了多少,照样能跑出漂亮的结果。其次,真正麻烦的往往是鞍点,它会让你损失降得特别慢,看着像收敛了其实还能再降。小张之前就吃过这个亏,他训练一个网络,损失降着降着就不怎么动了,他还以为已经收敛,后来仔细一看损失曲面,才发现是卡在了一片鞍点区域,换个优化器之后才继续往下走。所以大家后面才会折腾出各种优化器、聪明的初始化、归一化这一堆技巧,目的就是绕开这些坑,让训练能顺利往下走。
这跟各行各业做事是一个道理。说起来,做一道好菜其实不难,照着菜谱放盐放糖就行,线性模型就靠凸性这么保送你。但要把一家餐厅经营起来,那就得琢磨食材采购、菜单设计、客流节奏,深度网络就靠这一身优化技巧硬扛下来。小明之前跟我感慨过,说他刚开始学弹琴的时候,照着谱子弹下来就挺有成就感,后来要登台合奏,才发现节奏、力度、跟队友的呼吸全得对上,深度学习的训练也是同一个味儿,越往里钻越考验细节。偶尔我们放松一下,比如去跑两圈、或者看场球赛,其实也能体会到越是高水平的较量越考验细节这种感觉。PyTorch里那些五花八门的优化器,本质上都是在跟这个非凸曲面斗智斗勇。
8.受限优化:偶尔还得戴着镣铐跳舞
最后我们要认真聊聊受限优化,因为后面讲 SVM(2.12 节)会原封不动用到这一套。
有时候除了要让损失最小,我们还得满足某些限制条件,这种在限制下找最优的问题叫受限优化。写成标准形式是:
举个最直白的例子,你想让模型权重 尽量小(这叫正则化,后面讲权重衰减的时候会细讲),但又要求它满足某个条件,比如 (也就是向量长度的平方不超过 ,其中 表示向量 的长度,数学上叫范数)。这时候你不能让 随便乱跑,得在它不能超出的那个范围里找让损失最小的值。
处理这类问题有一个经典工具叫拉格朗日乘子法(Lagrange multiplier,拉格朗日是法国数学家)。它的思路是把限制条件塞进目标函数里,给每个约束 配一个乘子 ,组成一个新的拉格朗日函数:
原本戴着镣铐的问题,就转化成了对这个新函数求鞍点的问题。直觉上,最优解要么落在约束边界上(此时 ,约束"咬住"了),要么落在可行区域内部(此时约束没起作用,)。"咬住"还是"没咬住",由下面这组条件判断,这组条件有专门的 名字,叫 KKT 条件(Karush–Kuhn–Tucker,三位数学家的姓氏):
- 平稳性:,即 。
- 原始可行性:(解满足约束)。
- 对偶可行性:(乘子非负)。
- 互补松弛:。这一条最关键——它说"乘子和约束不能同时为正",要么约束咬住了边界(),此时 可以大于 0;要么约束没起作用(),此时 。
KKT 条件是凸优化里最优解的充要条件(非凸情况下是必要条件)。在 SVM 里,互补松弛那一条会直接告诉你"只有支持向量才起作用"——边界上的点 ,其余点 ,这就是支持向量机名字里"支持向量"四个字的由来。所以这一节别只当背景知识滑过去,2.12 节会把它当工具用。
9.收个尾
到这里我们这一章就讲得差不多了。一句话收束一下:凸的线性模型好训,非凸的深度网络难训但能训,这就是为什么后面整个系列要花那么多心思在优化器、初始化、归一化上。微积分那一套(导数、偏导数、梯度、泰勒展开)给我们提供了找方向的方法,凸优化告诉我们什么时候能放心地找,非凸的世界则逼着我们琢磨出更多工程上的办法。
数学底子打好之后,下一章我们就开始正式往神经网络里头钻,敬请期待。
练习
Q1. 凸函数最让人省心的性质是什么?这条性质对训练模型意味着什么?
凸函数的任何局部最小值就是全局最小值,严格凸的碗还有唯一的最低点。这对训练模型简直是泼天富贵——意味着梯度下降几乎一定能漂亮地收敛到最优解,根本不用愁会卡在半道上的某个坑里。这也是线性回归、逻辑回归、SVM 这些模型"理论干净、好训"的根,它们的目标函数都是凸的。
Q2. 设 ,在点 处梯度是多少?负梯度方向指向函数值上升还是下降?
对 求偏导把 当常数,得 ;对 求偏导 这项为零,得 。所以梯度 ,它指向函数值上升最快的方向;负梯度 才指向下降最快的方向,这就是梯度下降要走的路。
Q3. 用方向导数和 Cauchy–Schwarz 不等式解释,为什么梯度是函数值上升最快的方向?
方向导数 ( 是单位方向),由 Cauchy–Schwarz 不等式 ,等号成立当且仅当 和 同向。也就是说让方向导数最大的方向正是梯度的方向 ,反过来负梯度让方向导数最负,所以是最速下降方向。这也顺带说明最快下降的步长跟梯度长度有关,影响后面学习率怎么选。
Q4.(面试题) 用反证法证明:凸函数的局部最小值就是全局最小值。
设 是一个局部最小值(它附近一小片都比它大或相等),但假设它不是全局最小,即存在另一点 满足 。取凸组合 , 取很接近 1 的小正数让 落在 的邻域内。由凸性 ;因为 ,右边 ,于是 。可 在 邻域内而 是局部最小,邻域里不该有比它更小的点——矛盾!所以假设错, 必须是全局最小。