大白话 5 分钟带你走进人工智能 - 第六节梯度下降之从单元函数理解梯度下降过程 (1)

第六节梯度下降之从单元函数理解梯度下降过程 (1)

我们先来回顾下 多元线性回归整体的流程 是: 首先要有一个数据集,就是一个 x 矩阵和一个 y 向量,不再是一张表。x 矩阵的行是一个数据单位,或者叫一个数据点,列代表各种各样的特征。通常习惯把它标记为 m 行 n 列,也就是 x 里面有 m 条数据,n 个特征。现在手里有一个 m*n 的 x,y 是一个列向量,它的形状是 n 行 1 列。拿到这两数据之后,我们要根据已有的 x 矩阵和 y 向量,来预测一个新的 x 向量,它的 y 结果应该是什么?。x 的一条新数据的形状应该是 1*n,y 应该是一个 1*1 的形状。那么 x 向量乘以θ向量,就等于 y。因为 1*n·n*1=1*1,所以说θ是一个 n*1 的列向量。 所谓的线性回归就是要拿到一个 m*n 的 x 矩阵,拿到一个 n*1 的 y 向量,我们想要得到一个最合理的θ向量,并且它的形状是 n*1。 其实就是拿到一个数据集,想要得到一组最好的 w,将来用来预测新的数据,本质跟线性代数角度的考虑是一样的。

怎么得到这组θ?现在有 x 矩阵和 y 向量了,θ就等于\theta=(X^{T}X)^{-1}X^{T}y。x 的转置是一个 n*m 的矩阵,x 是一个 m*n 的矩阵,它俩的结果是一个 n*n 的,-1 次幂还是 n*n 的, 乘以 x 的转置 n*m,变成 n*m 的矩阵,再乘以 y(m*1),得 n*1,和θ的形状是一样的。当 mse 函数等于 0 的时候,求得了能够使 mse 最小的那组θ,为什么要求 mse 最小的θ?因为 mse 最小的时候似然函数最大,似然函数最大的时候,整个训练集上的 y 发生的总概率最大,这个时候是最合理的。所以当 MSE 最小的时候,这组θ就是最好的。 求 mse 最小值,其实求的不是函数的最小值,而是能够使函数达到最小值的θ,这个问题叫函数最优化问题。

线性回归的流程, 拿到训练集用算法训练出来一个模型,线性回归里模型是能用来预测未来新数据的东西。 线性回归里拿到一组 w 就能预测未来新数据的结果了。也就是在线性回归里,或者扩展到参数型模型里,只要拿到这一组 w 就有模型了,所以在参数型模型里边,训练出来的模型就是一列数,就是这组 w。未来用它,对位乘以 x 的不同特征值,就能得出结果。 模型没有那么复杂,它就是一组实数而已。 对解析解来说,训练模型是拿到 x,y 套到函数里面去算一下,就训练出来一个模型了,这个模型其实直接能够通过矩阵运算计算出来的。线性回归是要做预测未来的,想预测新数据,需要有 w,w 可以算出来,到目前为止有和无的问题已经解决了。 解析解有一个缺陷,scaling 的能力差 。昨天一瞬间就跑出来了,但随着数据集的增大,你会发现它不是再线性的递增消耗。不是说一万条数据,用十秒,十万条数据就用一百秒,有可能十万条数据用了一千秒,一万秒,它是 O(n3),它的缩放性太差了,scaling 是缩放规模的意思,就是可复制的情况太差了,小数据集上跑得很好,大数据量一来代码直接跑不动了,内存都溢出了,没法计算了。所以解析解在这方面是有问题的,而我们的实际情况说,小数据集几乎不存在,所以要给大家介绍一个非常重要的方法,叫 梯度下降法。

梯度下降法实际上是一个函数最优化问题的算法。 在深度学习里面,梯度下降法也是最常用的函数最优化方法,任何机器学习,包括深度学习,都离不开之前讲述的套路,有一个损失函数,在把损失函数优化好了之后,得到的结果就是要求的模型结果,基本都是这么一个套路。现在我们就想把这个损失函数独立的拆开来看,不去考虑它的意义,只考虑它是一个普通的函数,求它的能达到极小值的自变量的点就够了。 梯度下降法求解函数最小问题,求的是数值解。

什么叫解析解?什么叫数值解? 比如一个函数ax^{2}+bx+c的根\frac{-b+\sqrt{b^{2}-4ac}}{2a},这种提前把 x 和这些未知系数 abc 的关系给表示出来了,未来求 x 的时候,只要有了 abc,往这式子里一套,直接出结果的解,叫解析解。 什么叫数值解?比如有一个抛物线,有 f(x)的解析式了,给一个 x 就能算出来的结果。假如它不是一个二次函数,它是一个复杂的函数,你想求根,上来先蒙一个点,比如是 4, 能算出一个结果来,比如是 6,你发现这不是要求这个函数的根。把 x 变成 3,结果是 5,值变小了,离根进了一步。最后能试着找出来,使函数为零的那个点。它就没有通用性了,再给你另外一个函数,还是得重新的一步一步的算。那么这种求解的方式求出来的解叫数值解,就是通过一步一步试给试出来了,而不是说透析到了它的本质。怎么试这个结果效率更高,也应该有它的一些思想和套路,那么我们这种求解数值解的一些算法,就是教你如何试效率更高,能够尽快让你试出你要的数值解。

线性回归的损失函数,mse 函数是J(\theta )=\frac{1}{2}\sum_{i=1}^{m}(h_{\theta}x^{i}-y^{i})^{2},这个函数是一个凸函数,它只有一个最小值。即使是这样的一个函数,多复杂,只要它只有一个驻点(切线平了的地方)。

20190318203941223.png

在驻点左侧永远是单调递减的,在驻点右侧永远是单调递增的,这种函数就叫凸函数。凸函数其实是函数自由化问题里面最喜欢的一个函数。

求解数值解的一般套路: 随机出来一个初始值,此时肯定不是达到最好的值。对上一个值进行一次修改,本质上就是把它加上某一个数,加一个正数或者加一个负数,令其修改后的结果能更好一点,因为想求原函数J(\theta )=\frac{1}{2}\sum_{i=1}^{m}(h_{\theta}x^{i}-y^{i})^{2}的最小值。J 通常代表损失函数,J 是看θ而改变的,不同θ有不同的值。假如代 0.4 进去,能算出一结果来,因为知道 J(θ) 的解析式,就是 mse 函数,得到一个值比如是 1.6。代入 0.6,结果变成 1.2 了,说明更好一点。然后再代入 0.8,结果变成 0.4,更好了。代入 0.9,结果变成 0.6 了,变高了,说明在 0.8 跟 0.9 之间。是大体的思路,这么做太粗糙了,它永远不可能快。所以梯度下降法要比这种原始的想法要高级一些。但它整体的思路是一定要让结果越来越好才行,最后好到好不动了,就找到最终的结果了。对于梯度下降法,有函数的解析式 f(x),可以求出{f}'x的解析式。比如下图:

20190406171856702.png

假如初始点在 1 号点,右边是 2 号点,很显然 1 号点应该把值增大,2 号点减小,怎么通过计算的方式找到它们俩应该增大还是减小?0.4 代到原函数里得到一个 1.6,只看 1.6,看不出来它应该往哪走。但是有了原函数,就也有原函数的导函数了。0.4 带到导函数里(因为左边斜率为负)会得到一个负数,0.8 带进导函数会得到正数。也就是说它求导完了之后, 如果导函数对应的点为负数,那么 x 应该增加一点,如果为导函数对应的点为正数,x 应该减少一点, 才能接近最优值。也就是说它的导数,能够给我们提供一个方向,应该往哪走的方向。 导数的数值有意义吗? 假如导数求出来的数值越大,代表它越陡,凸函数远离谷底的时候越陡一点,那么说明离得越远,越陡体现在倒数上是数值越大。 假设只有一个 w 的情况,我们可以对J(w)求导,如果求导得到 -20,说明应该在 w 上加上一个数,因为是负的,加上一个正数,导数 (-20) 绝对值越大(说明值越小,越远离最低点),应该加的越多。于是我们干脆就把 -20 取个负号加上。也就是说梯度下降法的本质就是w+(-J'{(w)})。它大面上是合理的,但是有一个小小的问题,在于量纲不好统计。比如导数特别大,函数比较陡,比如下图:

20190406180141466.png

假如θ1 是 1 的,在θ1 求导是 -10,w+(-J'{(w)})就应该是 1+10,反而越过最好的θ变得上升了。假设θ2 是 11,在θ2 点求导是 +13,1+(-13)=-14,就又折回来了。虽然它每次都朝着正确的方向走,但走过了,怎么避免它走过?

通常是在(-J'{(w)})前乘一个λ,λ是自己定义的,通常它是 0.1,0.01,0.001,这种小数,来防止它别走过了。梯度能保证一个相对大小, 梯度的绝对值越大代表离得相对越远但是人为的加一个λ,相当于给它带了一刹车,都带上同一个λ,越陡我还是相对走得远,再远可能也没远哪去。 λ如果设置太小会迭代好多次,就计算次数变多了;设得过大会导致震荡,一下走过,好的震荡过的没过太多,坏的震荡直接震上去了,为什么?因为所谓的走多远,实际上落在 x 轴上走的,从左走到右,发现函数反倒变大了,这就是λ设得太大了,会导致它反着越震越往上跑。所以我们称这种 需要人为调整的这种参数 ,叫做 超参数(hyper parameter) 。hyper 代表超。为什么它叫超参数而不叫参数,是因为参数这个名字已经被用掉了, w 是参数,是客观的参数,学习出来的参数。而这些可以人为随便改的东西, 在参数之上的参数,所以它叫超参数。 λ是进入机器学习以后学得的第一个超参数,各种各样算法会有各种各样的超参数让人为的去设置。这也就是所谓的调参, 调的不是机器学习完了的这组 w,调的是超参数,是管控学习过程的一些开关。

举例:假设对一个一元函数 f(w) 来说,怎么找到最小值?比如 w 作为自变量,有一个初始 w,还有 f'(w)的解析式,就代表只要有了 w 就能求出结果了,那么 w1=w0+λ(-f'(w))。如果λ设置的没有大的失误的话,w1 应该比 w0 更好一些。w1 得到了,再带到 f'(w)里去,那么 w2=w1+λ(-f'(w1))。假如没有问题,此时 f'(w)绝对值应该变小了,然后一直这么弄,运算 k 次,那么 wk=wk-1+λ(-f'(wk-1))。k 次和 k-1 次没变化,代表已经在谷底了。最完美的是后边这一项算出来等于 0 了,它俩一点区别都没有了,说明已经找到导数为零的点了,肯定就是极小值。通常不会这么完美,因为λ有可能走过了,你会设第二个超参数,叫做 tolerance,代表前后两次的结果差值小到多少,就认为收敛了,有最小值了。 tolerance 通常这个值不需要人为的去设 ,但是它本身是个超参数,你确实可以更改的,是很小的一个值。当它小到一定程度了,我就认为它已经收敛了。所以 收敛就是下一次的结果跟上一次结果一样或者近似一样了。 如果λ设得太大,会导致它不收敛,越来越大,它永远会往下迭代,永远到不了两个值差很小的那种程度,这个叫做无法收敛。如果λ设太小了,会导致它达到收敛的运算次数变多了,对于计算机来说,一次走一米跟走一百米,就是一个加一和加一百,对它来说运算效率是一样的, 所以收敛次数的增加就是运算时间的增加。因此λ要设置的比较合理。