我们刚才找出来的 对应的误差是480. 这是由2017年~ 2020年的数据计算出的结果. 现在,不妨用这对 去预测下2021年初每日的观看次数. 我们预测2021年1月1日~2021年2月14日间的每日观看次数,计算出新的损失. 在2021年没有看过的数据上,损失用 来表示,值是580. 将预测结果绘制出来,如图1.6 所示,横轴代表距离2021年1月1日的天数,0代表2021年1月1日,图中最右边的点代表2021年2月14日;纵轴代表观看次数. 红色线是真实的观看次数,蓝色线是预测的观看次数. 可以看到,蓝色线几乎就是红色线往右平移一天而已,这很合理,因为目前的模型正是用某天观看次数乘以0.97,再加上100,来计算次日的观看次数.
图1.6 预估曲线图
这个真实的数据中有一个很神奇的现象:它是周期性的,每 7 天就会有两天(周五和周六)的观看次数特别少. 目前的模型只能向前看一天.一个模型如果能参考前 7 天的数据,也许能预测得更准,所以可以修改一下模型. 通常,一个模型的修改方向,往往来自我们对这个问题的理解,即领域知识.
一开始,由于对问题完全不理解,我们的模型是
(1.11)
这个只考虑1天的模型不怎么好.接下来,我们观测了真实的数据,得到一个结论:每 7 天是一个循环.所以要把前 7 天的观看次数都列入考虑. 现在,模型变成
(1.12)
其中, 代表前7天中第 天的观看次数,它们分别乘以不同的权重 ,加起来,再加上偏置,就可以得到预测的结果.该模型在训练数据(即2017年~ 2020年的数据)上的损失是380,而只考虑1天的模型在训练数据上的损失是480; 对于2021年1月1日~ 2021年2月14日的数据(以下简称2021年的数据)上,它的损失是490. 只考虑1天的模型的损失是580.
这个新模型中 和 的最优值如表1.1所示.
表1.1 和 的最优值
模型的逻辑是:7天前的数据跟要预测的数值关系很大,所以 是0.79,而其他几天则没有那么重要.
其实,可以考虑更多天的影响,比如28天,即
(1.13)
这个模型在训练数据上的损失是330,在2021年1月1日~ 2021年2月14日数据上的损失是460.如果考虑56天,即
(1.14)
则训练数据上的损失是320,2021年1月1日~ 2021年2月14日数据上的损失还是460.
可以发现,虽然考虑了更多天,但没有办法再降低损失. 看来考虑天数这件事,也许已经到了一个极限.把输入的特征 乘上一个权重,再加上一个偏置,得到预测的结果,这样的模型称为 线性模型(linear model) .
线性模型也许过于简单, 和 之间可能存在比较复杂的关系,如图1.7 所示.对于 的线性模型, 和 的关系就是一条斜率为正的直线,随着 越来越大, 也应该越来越大.设定不同的 可以改变这条直线的斜率,设定不同的 则可以改变这条直线和 轴的交点.但无论如何改变 和 ,它永远都是一条直线,永远都是 越大, 就越大:某一天的观看次数越多,次日的观看次数就越多.但在现实中,也许当 大于某个数值的时候,次日的观看次数反而会变少. 和 之间可能存在一种比较复杂的、像红色线一样的关系.但不管如何设置 和 ,我们永远无法用简单的线性模型构造红色线.显然,线性模型有很大的限制,这种来自模型的限制称为模型的偏差,无法模拟真实情况.
图1.7 线性模型的局限性
所以,我们需要写一个更复杂、更有灵活性、有更多未知参数的函数. 图1.8 中,红色线可以看作一个常数项 再加上一些 hard sigmoid 函数(hard sigmoid 函数的特性是当输入的值介于两个阈值间的时候,图像呈现出一个斜坡,其余位置都是水平的).常数项被设成红色线和 轴的交点一样大.第1个蓝色函数斜坡的起点,设在红色函数的起始地方. 第2个斜坡的终点设在第一个转角处,第1个蓝色函数的斜坡和红色函数的第1段斜坡斜率相同,这时候求 +❶, 就可以得到红色线左侧的线段.接下来,叠加第2个蓝色函数,所以第2个蓝色函数的斜坡就在红色函数的第1个转折点和第2个转折点之间,第2个蓝色函数的斜坡和红色函数的第2段斜坡斜率相同. 这时候求 +❶+❷,就可以得到红色函数左侧和中间的线段.对于第2个转折点之后的部分,再叠加第3个蓝色函数,第3个蓝色函数的斜坡的起点设在红色函数的第2个转折点,蓝色函数的斜坡和红色函数的第3段斜坡斜率相同. 最后,求 +❶+❷+❸,就得到了完整的红色线.
图1.8 构建红色线
所以红色线[即分段线性曲线(piecewise linear curve)]可以看作一个常数和一些蓝色函数的叠加.分段线性曲线越复杂,转折的点越多,所需的蓝色函数就越多.
也许要考虑的 和 的关系不是分段线性曲线 ,而是图1.9 所示的曲线. 可以在这样的曲线上先取一些点,再把这些点连起来,变成一条分段线性曲线.而这条分段线性曲线跟原来的曲线非常接近,如果点取得够多或位置适当,分段线性曲线就可以逼近连续曲线,甚至可以逼近有角度和弧度的连续曲线. 我们可以用分段线性曲线来逼近任何连续曲线,只要有足够的蓝色函数.
图1.9 分段线性曲线可以逼近任何连续曲线
和 的关系非常复杂也没关系,可以想办法写一个带有未知数的函数.直接写 hard sigmoid 函数不是很容易,但可以用 sigmoid 函数来逼近 hard sigmoid 函数,如图1.10 所示.sigmoid函数的表达式为
其中,输入是 ,输出是 , 为常数.
图1.10 使用 sigmoid 函数逼近 hard sigmoid 函数
当 的值趋近于正无穷的时候, 这一项就会几乎消失, 就会收敛于常数 ;当 的值趋近于负无穷的时候,分母就会非常大, 就会收敛于 0.
所以可以用这样的一个函数逼近蓝色函数.为了简洁,蓝色函数的表达式记为
(1.15)
其中
(1.16)
调整式(1.15)中的 、 和 , 就可以构造各种不同形状的 sigmoid函数,从而用各种不同形状的 sigmoid函数逼近 hard sigmoid 函数.如图1.11 所示,如果调整 ,就会改变斜坡的坡度;如果调整 ,就可以左右移动sigmoid函数曲线;如果调整 ,就可以改变曲线的高度.所以,只要叠加拥有不同的 、 不同的 和不同的 的sigmoid函数,就可以逼近各种不同的分段线性函数(如图1.12 所示):
(1.17)
图1.11 调整参数,构造不同的 sigmoid 函数
图1.12 使用 hard sigmoid 函数来合成红色线
此外,我们可以不只用一个特征 ,而是用多个特征代入不同的 、 、 ,构建出各种不同的sigmoid函数,从而得到更有 灵活性(flexibility) 的分段线性函数,如图1.13 所示. 可以用 来代表特征的编号. 如果要考虑 28 天的数据, 就可以取 1 ~ 28.
图1.13 构建更有灵活性的函数
举个只考虑3个特征(即只考虑前3天~前1天)的例子. 此时 可以取 1、2、3,每一个 就代表一个蓝色函数. 每一个蓝色函数都用一个 sigmoid函数来近似,一共需要3个 sigmoid函数:
(1.18)
代表在第 个 sigmoid 函数中乘给第 个特征的权重.设图1.13 的蓝色虚线框中有
(1.19)
我们可以用矩阵和向量相乘的方法,得到如下比较简洁的写法.
(1.20)
也可以改成线性代数比较常用的表示方式,如下所示.
(1.21)
对应的是 、 、 .有了 、 、 , 分别通过 sigmoid函数得到 、 、 ,即
(1.22)
因此,如图1.14 所示,蓝色虚线框里面做的事情,就是从 、 、 得到 、 、 .
图1.14 比较有灵活性函数的计算过程
上面这个比较有灵活性的函数可以用线性代数来表示,即
(1.23)
接下来,如图1.15 所示, 是特征,绿色的 是向量,灰色的 是数值. 、 、 、 是未知参数.把矩阵展平,与其他项“拼合”,就可以得到一个很长的向量. 把 的每一行或每一列拿出来,拿行或拿列都可以. 先把 的每一列或每一行“拼”成一个长向量,再把 、 、 “拼”进来,这个长向量可以直接用 来表示. 我们将所有未知参数一律统称 .
图1.15 将未知参数“拼”成一个向量
Q: 优化是找一个可以让损失最小的参数,是否可以穷举所有可能的未知参数的值?
A:在只有 和 两个参数的前提下,可以穷举所有可能的 和 的值. 所以在参数很少的情况下,甚至可能不用梯度下降,也不需要优化技巧. 但是当参数非常多的时候,就不能使用穷举的方法,而应使用梯度下降的方法找出可以让损失最小的参数.
Q:刚才的例子里面有 3 个 sigmoid函数,为什么是 3 个,能不能是 4 个或更多个?
A:sigmoid 函数的数量由我们自己决定,sigmoid 函数的数量越多,可以产生的分段线性函数就越复杂.sigmoid 函数的数量也是一个超参数.
接下来定义损失. 此前的损失函数记作 ,现在未知参数太多了,所以直接用 来统设所有的参数,损失函数记作 . 损失函数能够判断 的好坏,计算方法跟只有两个参数的情况是一样的:先给定 的值,即某一组 、 、 、 的值,再把特征 加进去,得到估测出来的 ,最后计算一下跟真实标签之间的误差. 把所有的误差通通加起来,就得到了损失.
下一步就是优化 ,即优化
(1.24)
要找到一组 , 让损失越小越好,可以让损失最小的一组 称为 .一开始,要随机选一个初始的数值 . 接下来计算每一个未知参数对 的微分,得到向量 为
(1.25)
(1.26)
假设有 1000 个参数,向量 的长度就是 1000,这个向量也称为梯度向量. 代表梯度; 是指计算梯度的位置,也就是 等于 的地方.计算出 以后,接下来更新参数. 代表起始值,它是一个随机选的起始值,代表 更新过一次的结果. 用 减掉微分结果和 的积,得到 ,以此类推,就可以把 1000 个参数都更新了(见图1.16):
(1.27)
(1.28)
图1.16 使用梯度下降更新参数
参数有 1000 个, 就是 1000 个数值, 是1000 维的向量, 也是 1000 维的向量.整个操作就是这样,由 开始计算梯度,根据梯度把 更新成 ;再算一次梯度,再根据梯度把 更新成 ;以此类推,直到不想做,或者梯度为零,导致无法再更新参数为止. 不过在实践中,几乎不太可能梯度为零,通常停下来就是因为我们不想做了.
实现上,有个细节上的区别,如图1.17 所示,实际使用梯度下降时,会把 笔数据随机分成一个个的 批量(batch) ,每个批量里面有 笔数据.本来是把所有的数据拿出来计算损失 ,现在只拿一个批量里面的数据出来计算损失,记为 .假设 够大,也许 和 会很接近.所以在实现上,每次会先选一个批量,用该批量来算 ,根据 来算梯度,再用梯度来更新参数;接下来再选下一个批量,算出 ,根据 算出梯度,再更新参数;最后再取下一个批量,算出 ,根据 算出梯度,再用 算出来的梯度更新参数.
图1.17 分批量进行梯度下降
把所有的批量都看过一遍的过程称为一个 回合(epoch) ,每更新一次参数称为一次更新. 更新和回合是两个不同的概念.
举个例子,假设有 10 000 笔数据,即 等于 10 000; 批量大小(batch size) 设为 10,即 等于10.10 000 个 样本(example) 形成了 1000 个批量,所以在一个回合里面更新了参数 1000 次,所以一个回合不只更新参数一次.
再举个例子,假设有 1000 个数据,批量大小设为 100,批量大小和sigmoid 函数的个数都是超参数.1000 个 样本,批量大小 设为 100,1个回合总共更新10次参数.一个回合的训练其实不知道更新了几次参数,有可能 1000 次,也有可能 10 次,取决于批量有多大.
其实还可以对模型做更多的变形,不一定要把 hard sigmoid 函数换成 soft sigmoid函数. hard sigmoid 函数可以看作两个 ReLU(Rectified Linear Unit,修正线性单元) 的叠加,ReLU先是一条水平的线,到了某个地方经过一个转折点,变成一个斜坡,对应的公式为
(1.29)
旨在看 0 和 哪个比较大,比较大的值会被当作输出:如果 ,输出是0;如果 ,输出是 . 如图1.18 所示,通过 、 、 可以挪动ReLU的位置和斜率. 把两个 ReLU 叠起来,就可以变成 hard sigmoid函数. 想要用 ReLU,就把前文用到 sigmoid 函数的地方换成 .
图1.18 ReLU
如图1.19 所示,两个 ReLU才能够合成一个 hard sigmoid函数.要合成 个 hard sigmoid函数,需要 个 sigmoid函数. 如果要用 ReLU 做到一样的事情,则需要 个 ReLU,因为两个 ReLU 合起来才是一个 hard sigmoid函数.表示一个 hard sigmoid 函数不是只有一种做法. 在机器学习里面,sigmoid 函数或 ReLU 称为 激活函数(activation function) .
图1.19 激活函数
当然还有其他常见的激活函数,但 sigmoid 函数和 ReLU 是最为常见的激活函数. 接下来的实验都选择用了 ReLU,显然 ReLU 比较好. 对于使用前56天数据的情况,实验结果如图1.20 所示.
图1.20 激活函数实验结果
连续使用 10个ReLU作为模型,跟使用线性模型的结果差不多.但连续使用 100 个 ReLU作为模型,结果就有显著差别了. 100 个 ReLU 在训练数据上的损失就可以从 320 降到 280,在测试数据上的损失也低了一些.接下来使用 1000 个 ReLU 作为模型,在训练数据上 损失 更低了一些,但是在模型没看过的数据上,损失没有变化.
接下来可以继续改进模型,如图1.21 所示,从 变成 ,就是把 乘上 再加 ,最后通过 sigmoid函数(不一定要通过 sigmoid函数,通过 ReLU 也可以得到 ). 可以增加网络的层数,将同样的事情再反复多做几次:把 做这一连串的运算产生 ,接下来把 做这一连串的运算产生 ,等等.反复的次数是另一个超参数.注意, 、 和 、 不是同一组参数,这里增加了更多的未知参数.
图1.21 改进模型
每多做一次上述的事情,我们就添加了 100 个 ReLU. 依然考虑前 56 天的数据,实验结果如图1.22 所示,对于2017年~ 2020年的数据,如果做两次(2层),损失降低很多,从280 降到 180.如果做3次(3层),损失从 180 降到 140. 而在2021年的数据上,通过3次ReLU,损失从 430 降到了 380.
图1.22 使用 ReLU 的实验结果
通过3次 ReLU 的实验结果如图1.23 所示,其中红色线是真实数据,蓝色线是预测出来的数据. 看红色线,每隔一段时间,就会有低点,在低点的地方,机器的预测还是很准确的.机器高估了真实的观看次数,尤其是红圈标注的这一天. 这一天有一个很明显的低谷,但是机器没有预测到这一天有明显的低谷,它晚一天才预测出低谷.
图1.23 使用3次 ReLU 的实验结果
如图1.24 所示,sigmoid 函数或 ReLU 称为神经元(neuron), 神经网络(neural network) 就是由神经元组成的.这些术语来自真实的人脑,人脑中有很多真实的神经元,很多神经元组成神经网络.图1.24 中的每一列神经元称为神经网络的一层,又称为 隐藏层(hidden layer) ,隐藏层多的神经网络就“深”,称为深度神经网络.
图1.24 神经网络的结构
神经网络正向越来越深的方向发展,2012 年的 AlexNet有 8 层,在图像识别上的错误率为 16.4%. 两年之后的 VGG 有19层,错误率降至 7.3 %.后来的 GoogleNet 有 22 层,错误率降至 6.7%. 残差网络(Residual Network,ResNet) 有 152 层,错误率降至3.57%.
刚才只做到 3 层,我们应该做得更深,现在的神经网络都是几百层的,深度神经网络还要更深.但 4 层的网络在训练数据上的损失 是 100,在2021年的数据上的损失是440. 在训练数据上,3 层的网络表现比较差;但是在2021年的数据上,则是4 层的网络表现比较差,如图1.25 所示.模型在训练数据和测试数据上的结果不一致,这种情况称为 过拟合(overfitting) .
图1.25 模型有过拟合问题
到目前为止,我们还没有真正发挥这个模型的力量,2021年 2 月 14日之前的数据是已知的.要预测未知的数据,选 3 层的 网络还是 4 层的 网络 呢?假设今天是 2 月 26 日,今天的观看次数是未知的. 如果用已经训练出来的神经网络预测今天的观看次数,就要选 3 层的网络,虽然 4 层的网络在训练数据上的结果比较好,但模型对于它没看过的数据的预测结果更重要.
深度神经网络的训练会用到 残差网络(Residual Network,ResNet) ,这是一种比较有效率的梯度计算方法.
训练数据和测试数据如式(1.30) 所示. 对于模型来说,测试数据只有 而没有 ,我们正是使用模型在测试数据上的预测结果来评估模型的性能.
(1.30)
训练数据用来训练模型,训练过程如下.
(1) 写出一个有未知数 的函数, 代表一个模型里面所有的未知参数.这个函数记作 ,意思是函数名叫 ,输入的特征为 .
(2) 定义损失,损失是一个函数,其输入就是一组参数,旨在判断这组参数的好坏.
(3) 解一个优化的问题,找一个 ,让损失越小越好. 能让损失最小的 记为 ,即
(1.31)
有了 以后,就可以把它拿来用在测试数据上,也就是把 代入这些未知的参数. 本来 里面就有一些未知的参数,现在用 替代 ,输入测试数据,将输出的结果保存起来,就可以用来评估模型的性能.