核心学习目标:通过《深度学习详解》和 李宏毅老师 21年的机器学习课程视频,入门机器学习,并尝试学习深度学习,展开代码实践(选修)。该书保留了李宏毅老师公开课中大量生动有趣的例子,帮助读者从生活化的角度理解深度学习的概念、建模过程和核心算法细节,包括卷积神经网络、Transformer、生成模型、自监督学习(包括 BERT 和 GPT)等深度学习常见算法,对抗攻击、领域自适应、强化学习、元学习、终身学习、网络压缩等深度学习相关进阶算法。
跟李宏毅学深度学习(入门) - 学习说明https://linklearner.com/activity/16/14/40
第 1 章 机器学习基础
一、基本概念
机器学习(Machine Learning,ML): 机器具备有学习的能力。
深度学习(Deep Learning,DL): 一种基于神经网络的机器学习方法,通过学习数据的多层次表示来识别模式和特征。
回归(regression): 找一个函数的任务。找的函数的输出是一个数值,一个标量(scalar)。
分类(classification):让机器做选择题。人类先准备好一些选项,这些选项称为类别(class),现在要找的函数的输出就是从设定好的选项里面选择一个当作输出,该任务称为分类。
结构化学习(structured learning):机器产生有结构的东西的问题。
二、案例学习
以视频的点击次数预测为例介绍下机器学习的运作过程。假设有人想要通过视频平台赚钱,他会在意频道有没有流量,这样他才会知道他的获利。假设后台可以看到很多相关的信息,比如:每天点赞的人数、订阅人数、观看次数。根据一个频道过往所有的信息可以预测明天的观看次数。找一个函数,该函数的输入是后台的信息,输出是隔天这个频道会有的总观看的次数。机器学习找函数的过程,分成 3 个步骤。
第1个步骤
写出一个带有未知参数的函数 f,其能预测未来观看次数。其中,y 是准备要预测的东西,要预测的是今天(2 月 26 日)这个频道总共观看的人,y 就假设是今天总共的观看次数。x1 是这个频道,前一天(2 月 25 日)总共的观看次数,y 跟 x1 都是数值,b 跟 w 是未知的参数,它是准备要通过数据去找出来的,w 跟 b 是未知的。总而言之,y = b + w ∗ x1,而 b 跟 w 是未知的。
模型(model):带有未知的参数(parameter)的函数。模型在机器学习里面,就是一个带有未知的参数的函数,特征(feature) x1 是这个函数里面已知的,它是来自于后台的信息,2 月 25 日点击的总次数是已知的,而 w 跟 b 是未知的参数。w 称为权重(weight),b 称为偏置。(bias)。
第2个步骤
定义损失(loss),损失也是一个函数。这个函数的输入是模型里面的参数,模型是 y = b + w ∗ x1,而 b 跟 w 是未知的,损失是函数 L(b, w),其输入是模型参数 b 跟w。损失函数输出的值代表,现在如果把这一组未知的参数,设定某一个数值的时候,这笔数值好还是不好。
eg:假设未知的参数的设定是 b = 500,w = 1,预测未来的观看次数的函数就变成 y = 500 + x1。要从训练数据来进行计算损失,在这个问题里面,训练数据是这一个频道过去的观看次数。举个例子,从 2017 年 1 月 1 日到 2020 年 12 月 31 日的观看次数(此处的数字是随意生成的)如图1.1所示,接下来就可以计算损失。
在这个等高线图上面,越偏红色系,代表计算出来的损失越大,就代表这一组 w 跟 b 越差。如果越偏蓝色系,就代表损失越小,就代表这一组 w 跟 b 越好,拿这一组 w 跟 b,放到函数里面,预测会越精准。假设 w = −0.25, b = −500,这代表这个频道每天看的人越来越少,而且损失这么大,跟真实的情况不太合。如果 w = 0.75, b = 500,估测会比较精准。如果 w 代一个很接近 1 的值,b 带一个小小的值,比如说 100 多,这个时候估测是最精准的,这跟大家的预期可能是比较接近的,就是拿前一天的点击的总次数,去预测隔天的点击的总次数,可能前一天跟隔天的点击的总次数是差不多的,因此 w 设 1,b 设一个小一点的数值,也许估测就会蛮精准的。如图 1.2 所示的等高线图,就是试了不同的参数,计算它的损失,画出来的等高线图称为误差表面(error surface)。这是机器学习的第 2 步。
第3个步骤
解一个最优化的问题。找一个 w 跟 b,把未知的参数找一个数值出来,看代哪一个数值进去可以让损失 L 的值最小,就是要找的 w 跟 b,这个可以让损失最小的 w 跟 b 称为 w∗ 跟 b∗ 代表它们是最好的一组 w 跟 b,可以让损失的值最小。
先假设只有一个未知的参数 w,b 是已知的。w 代不同的数值的时候,就会得到不同的损失,这一条曲线就是误差表面,只是刚才在前一个例子里面,误差表面是 2 维的,这边只有一个参数,所以这个误差表面是 1 维的。如图 1.3 所示,计算在 w0 这个位置的误差表面的切线斜率,也就是这一条蓝色的虚线,它的斜率,如果这一条虚线的斜率是负的,代表说左边比较高,右边比较低。在这个位置附近,左边比较高,右边比较低。如果左边比较高右边比较低的话,就把 w 的值变大,就可以让损失变小。如果算出来的斜率是正的,就代表左边比较低右边比较高。左边比较低右边比较高,如果左边比较低右边比较高的话,就代表把 w 变小了,w 往左边移,可以让损失的值变小。这个时候就应该把 w 的值变小。
图 1.4 所示的例子里面,把 w 设定在最右侧红点附近这个地方可以让损失最小。但如果在梯度下降中,w0 是随机初始的位置,也很有可能走到 wT 这里,训练就停住了,无法再移动 w 的位置。右侧红点这个位置是真的可以让损失最小的地方,称为全局最小值(global minima),而 wT 这个地方称为局部最小值(local minima),其左右两边都比这个地方的损失还要高一点,但是它不是整个误差表面上面的最低点。
所以常常可能会听到有人讲到梯度下降不是个好方法,这个方法会有局部最小值的问题,无法真的找到全局最小值。事实上局部最小值是一个假问题,在做梯度下降的时候,真正面对的难题不是局部最小值。有两个参数的情况下使用梯度下降,其实跟刚才一个参数没有什么不同。如果一个参数没有问题的话,可以很快的推广到两个参数。
如图 1.5 所示,随便选一个初始的值,先计算一下 w 对 L 的微分,跟计算一下 b 对 L 的微分,接下来更新 w 跟 b,更新的方向就是 ∂L/∂w,乘以 η 再乘以一个负号,∂L/∂b,算出这个微分的值,就可以决定更新的方向,可以决定 w 要怎么更新。把 w 跟 b 更新的方向结合起来,就是一个向量,就是红色的箭头,再计算一次微分,再决定要走什么样的方向,把这个微分的值乘上学习率,再乘上负号,我们就知道红色的箭头要指向那里,就知道如何移动 w 跟 b 的位置,一直移动,期待最后可以找出一组不错的 w, b。
三、线性模型
如图 1.6 所示,横轴是代表的是时间,所以 0 这个点代表的是 2021 年 1 月 1 日,最右边点代表的是 2021 年 2 月 14 日,纵轴就是观看的人次,这边是用千人当作单位。红色线是真实的观看人次,蓝色线是机器用这一个函数预测出来的观看人次。蓝色的线几乎就是红色的线往右平移一天而已,这很合理,因为 x1 也就是前一天的观看人次,跟隔天观看人次的,要怎么拿前一天的观看人次,去预测隔天的观看人次呢,前一天观看人次乘以 0.97,加上 100 加上100,就是隔天的观看人次。机器几乎就是拿前一天的观看人次来预测隔天的观看人次。这个真实的数据有一个很神奇的现象,它是有周期性的,它每隔 7 天就会有两天特别低(周五和周六),两天观看的人特别少,每隔 7 天,就是一个循环。
考虑更多天没有办法再更降低损失了。看来考虑天数这件事,也许已经到了一个极限。这些模型都是把输入的特征 x 乘上一个权重,再加上一个偏置就得到预测的结果,这样的模型称为线性模型(linear model)。接下来会看如何把线性模型做得更好。
分段线性曲线
线性模型也许过于简单,x1 跟 y 可能中间有比较复杂的关系,如图 1.7 所示。对于线性模型,x1 跟 y 的关系就是一条直线,随着 x1 越来越高,y 就应该越来越大。设定不同的 w可以改变这条线的斜率,设定不同的 b 可以改变这一条蓝色的直线跟 y 轴的交叉点。但是无论如何改 w 跟 b,它永远都是一条直线,永远都是 x1 越大,y 就越大,前一天观看的次数越多,隔天的观看次数就越多。
蓝线 1 函数斜坡的起点,设在红色函数的起始的地方,第 2 个斜坡的终点设在第一个转角处,让第 1 个蓝色函数的斜坡和红色函数的斜坡的斜率是一样的,这个时候把 0+1 就可以得到红色曲线左侧的线段。接下来,再加第 2 个蓝色的函数,所以第2 个蓝色函数的斜坡就在红色函数的第一个转折点到第 2 个转折点之间,让第 2 个蓝色函数的斜率跟红色函数的斜率一样,这个时候把 0+1+2,就可以得到红色函数左侧和中间的线段。接下来第 3 个部分,第 2 个转折点之后的部分,就加第 3 个蓝色的函数,第 3 个蓝色的函数坡度的起始点设的跟红色函数转折点一样,蓝色函数的斜率设的跟红色函数斜率一样,接下来把 0+1+2+3 全部加起来,就得到完整红色的线。
红色线,即分段线性曲线(piecewise linear curve)可以看作是一个常数,再加上一堆蓝色的函数。分段线性曲线可以用常数项加一大堆的蓝色函数组合出来,只是用的蓝色函数不一定一样。要有很多不同的蓝色函数,加上一个常数以后就可以组出这些分段线性曲线。如果分段线性曲线越复杂,转折的点越多,所需的蓝色函数就越多。如图 1.8 所示
也许要考虑的 x 跟 y 的关系不是分段线性曲线,而是如图 1.9 所示的曲线。可以在这样的曲线上面,先取一些点,再把这些点点起来,变成一个分段线性曲线。而这个分段线性曲线跟原来的曲线,它会非常接近,如果点取的够多或点取的位置适当,分段线性曲线就可以逼近这一个连续的曲线,就可以逼近有角度的、有弧度的这一条曲线。 所以可以用分段线性曲线去逼近任何的连续的曲线,而每个分段线性曲线都可以用一大堆蓝色的函数组合起来。也就是说,只要有足够的蓝色函数把它加起来,就可以变成任何连续的曲线。
假设 x 跟 y 的关系非常复杂也没关系,就想办法写一个带有未知数的函数。直接写 HardSigmoid 不是很容易,但是可以用一条曲线来理解它,用 Sigmoid 函数来逼近 Hard Sigmoid,如图 1.10 所示。其横轴输入是 x1,输出是 y,c 为常数。
如图 1.11 所示,如果改 w,就会改变斜率,就会改变斜坡的坡度。如果改了 b,就可以把这一个 Sigmoid 函数左右移动;如果改 c,就可以改变它的高度。所以只要有不同的 w 不同的 b 不同的 c,就可以制造出不同的 Sigmoid 函数,把不同的Sigmoid 函数叠起来以后就可以去逼近各种不同的分段线性函数;分段线性函数可以拿来近似各种不同的连续的函数。
如图 1.12 所示,红色这条线就是 0 加 1+2+3,而 1、2、3 都是蓝色的函数,其都可写成 (b + wx1),去做 Sigmoid 再乘上 ci1,只是 1、2、3 的 w、b、c 不同。
此外,我们可以不只用一个特征 x1,可以用多个特征代入不同的 c, b, w,组合出各种不同的函数,从而得到更有灵活性(flexibility)的函数,如图 1.13 所示。用 j 来代表特征的编号。如果要考虑前 28 天,j 就是 1 到 28。
Q: 优化是找一个可以让损失最小的参数,是否可以穷举所有可能的未知参数的值?
A:只有 w 跟 b 两个参数的前提之下,可以穷举所有可能的 w 跟 b 的值,所以在参数很少的情况下。甚至可能不用梯度下降,不需要优化的技巧。但是参数非常多的时候,就不能使用穷举的方法,需要梯度下降来找出可以让损失最低的参数。
接下来要定义损失。之前是 L(w, b),因为 w 跟 b 是未知的。现在未知的参数很多了,再把它一个一个列出来太累了,所以直接用 θ 来统设所有的参数,所以损失函数就变成 L(θ)。损失函数能够判断 θ 的好坏,其计算方法跟刚才只有两个参数的时候是一样的。
假设参数有 1000 个,θ0 就是 1000 个数值,1000 维的向量,g 是 1000 维的向量,θ1 也是 1000 维的向量。 整个操作就是这样,由 θ0 算梯度,根据梯度去把 θ0 更新成 θ1,再算一次梯度,再根据梯度把 θ1 再更新成 θ2,再算一次梯度把 θ2 更新成 θ3,以此类推,直到不想做。或者计算出梯度为 0 向量,导致无法再更新参数为止,不过在实现上几乎不太可能梯度为 0,通常会停下来就是我们不想做了。
但实现上有个细节的问题,实际使用梯度下降的时候,如图 1.17 所示,会把 N 笔数据随机分成一个一个的批量(batch),一组一组的。每个批量里面有 B 笔数据,所以本来有 N笔数据,现在 B 笔数据一组,一组叫做批量。本来是把所有的数据拿出来算一个损失,现在只拿一个批量里面的数据出来算一个损失,记为 L1 跟 L 以示区别。假设 B 够大,也许 L 跟L1 会很接近。所以实现上每次会先选一个批量,用该批量来算 L1,根据 L1 来算梯度,再用梯度来更新参数,接下来再选下一个批量算出 L2,根据 L2 算出梯度,再更新参数,再取下一个批量算出 L3,根据 L3 算出梯度,再用 L3 算出来的梯度来更新参数。所以并不是拿 L 来算梯度,实际上是拿一个批量算出来的 L1, L2, L3 来计算梯度。把所有的批量都看过一次,称为一个回合(epoch),每一次更新参数叫做一次更新。更新跟回合是不同的东西。每次更新一次参数叫做一次更新,把所有的批量都看过一遍,叫做一个回合。更新跟回合的差别,举个例子,假设有 10000 笔数据,即 N 等于 10000,批量的大小是设 10,也就 B 等于 10。10000 个样本(example)形成了 1000 个批量,所以在一个回合里面更新了参数 1000 次,所以一个回合并不是更新参数一次,在这个例子里面一个回合,已经更新了参数 1000 次了。第 2 个例子,假设有 1000 个数据,批量大小(batch size)设 100,批量大小和 Sigmoid的个数都是超参数。1000 个样本,批量大小设 100,1 个回合总共更新 10 次参数。所以做了一个回合的训练其实不知道它更新了几次参数,有可能 1000 次,也有可能 10 次,取决于它的批量大小有多大。
模型变形
通过 w, b, c 可以挪动其位置和斜率。把两个 ReLU 叠起来就可以变成 Hard 的 Sigmoid,想要用 ReLU,就把 Sigmoid 的地方,换成max(0, bi + wijxj )。
如图 1.19 所示,2 个 ReLU 才能够合成一个 Hard Sigmoid。要合成 i 个 Hard Sigmoid,需要 i 个 Sigmoid,如果 ReLU 要做到一样的事情,则需要 2i 个 ReLU,因为 2 个 ReLU 合起来才是一个 Hard Sigmoid。因此表示一个 Hard 的 Sigmoid 不是只有一种做法。在机器学习里面,Sigmoid 或 ReLU 称为激活函数(activation function)。
当然还有其他常见的激活函数,但 Sigmoid 跟 ReLU 是最常见的激活函数,接下来的实验都选择用了 ReLU,显然 ReLU 比较好,实验结果如图 1.20 所示。如果是线性模型,考虑56 天,训练数据上面的损失是 320,没看过的数据 2021 年数据是 460。连续使用 10 个 ReLU作为模型,跟用线性模型的结果是差不多。
接下来可以继续改模型,如图 1.21 所示,从 x 变成 a,就是把 x 乘上 w 加 b,再通过Sigmoid 函数。不一定要通过 Sigmoid 函数,通过 ReLU 也可以得到 a,同样的事情再反复地多做几次。 所以可以把 x 做这一连串的运算产生 a,接下来把 a 做这一连串的运算产生 a′。反复地多做的次数又是另外一个超参数。注意,w, b 和 w′, b′ 不是同一个参数,是增加了更多的未知的参数。
每次都加 100 个 ReLU,输入特征,就是 56 天前的数据。如图 1.22 所示,如果做两次,损失降低很多,280 降到 180。如果做 3 次,损失从 180 降到 140,通过 3 次 ReLU,从 280降到 140,在训练数据上,在没看过的数据上,从 430 降到了 380。
通过 3 次 ReLU 的实验结果如图 1.23 所示。横轴就是时间,纵轴是观看次数。红色的线是真实的数据,蓝色的线是预测出来的数据在这种低点的地方啊,看红色的数据是每隔一段时间,就会有两天的低点,在低点的地方,机器的预测还算是蛮准确的,机器高估了真实的观看人次,尤其是在红圈标注的这一天,这一天有一个很明显的低谷,但是机器没有预测到这一天有明显的低谷,它是晚一天才预测出低谷。这天最低点就是除夕。但机器只知道看前 56 天的值,来预测下一天会发生什么事,所以它不知道那一天是除夕。
如图 1.24 所示,Sigmoid 或 ReLU 称为神经元(neuron),很多的神经元称为神经网络(neural network)。人脑中就是有很多神经元,很多神经元串起来就是一个神经网络,跟人脑是一样的。人工智能就是在模拟人脑。神经网络不是新的技术,80、90 年代就已经用过了,后来为了要重振神经网络的雄风,所以需要新的名字。每一排称为一层,称为隐藏层(hiddenlayer),很多的隐藏层就“深”,这套技术称为深度学习。
所以人们把神经网络越叠越多越叠越深,2012 年的 AlexNet 有 8 层它的错误率是 16.4%,两年之后 VGG 有 19 层,错误率在图像识别上进步到 7.3 %。这都是在图像识别上一个基准的数据库(ImageNet)上面的结果,后来 GoogleNet 有 22 层,错误率降到 6.7%。而残差网络(Residual Network,ResNet)有 152 层,错误率降到 3.57%。
刚才只做到 3 层,应该要做得更深,现在网络都是叠几百层的,深度学习就要做更深。但4 层在训练数据上,损失是 100,在没有看过 2021 年的数据上,损失是 440。在训练数据上,层比 4 层差,但是在没看过的数据上,4 层比较差,3 层比较好,如图 1.25 所示。在训练数据和测试数据上的结果是不一致的,这种情况称为过拟合(overfitting)。
Task 2 《深度学习详解》- 1.2 了解线性模型https://linklearner.com/activity/16/14/55