机器学习 之 牛顿法和梯度下降法原理与实现

泰勒定理

f(x) x0 点可展开为幂级数 f(x)=i=0aif(xx0)i ,则 f(x) x0 N(x0,σ) 邻域内有任意阶导数,且系数 an=f(n)(x0)n! 。因此

f(x)=i=0aif(xx0)i=i=0f(i)(x0)i!f(xx0)i

称为 f(x) x0 的泰勒级数,系数称为泰勒系数。当 x0=0 时,称为麦克劳林级数。

牛顿法求根

牛顿法的求根方法其实就是泰勒公式的一阶展开。

首先平方根的函数 y=x 构造以 y 为自变量的函数 

f(y)=y2x

将其按照泰勒公式进行一阶展开后,得到 
f(y)=f(y0)+f(y0)(yy0)=0

移项后得到 
y=y0f(y0)f(y0)

因此得到一阶展开后的通项公式 
yn+1=ynf(yn)f(yn)

将公式带入后,得到 
yn+1=yny2nx2yn=12(ynxyn)

<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span style="font-family:Times New Roman;font-size:18px;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">double</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sqrt</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">double</span> x){
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">double</span> y = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0</span>;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span>(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">fabs</span>(y * y - x) >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1e-9</span>){
        y = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5</span> * (y - x / y ); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 对应上面的公式</span>
    } 
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> y;
}</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">1</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">2</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">3</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">4</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">5</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">6</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">7</span></li></ul>

牛顿法求平方根倒数

魔数的平方根倒数算法。

<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span style="font-family:Times New Roman;font-size:18px;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> Q_rsqrt( <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> number )
{
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> i;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x2, y;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> threehalfs = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.5F</span>;
    x2 = number * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5F</span>;
    y  = number;
    i  = * ( <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> * ) &y;                       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// evil floating point bit level hacking</span>
    i  = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x5f3759df</span> - ( i >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> );               <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// what the fuck?</span>
    y  = * ( <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 1st iteration </span>
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> y;
}</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">1</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">2</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">3</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">4</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">5</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">6</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">7</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">8</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">9</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">10</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">11</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">12</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">13</span></li><li style="box-sizing: border-box; padding: 0px 5px;"><span style="font-family:Times New Roman;font-size:18px;">14</span></li></ul>

首先求平方根的倒数 y=1x 可以构造以y为自变量的函数,由如下函数表示 

f(y)=1y2x=0

其一阶泰勒展开的通项公式为 
yn+1=ynf(yn)f(yn)=yn(3xy2n)2=yn(1.5xy2n2)

该公式对应的代码为y = y * ( threehalfs - ( x2 * y * y ) ),但是由于那个魔数的存在,它只需要迭代一轮。

梯度

假设函数 u=f(x,y,z) p(x,y,z) 可微,那么

  1. fx(x,y,z) 是函数在x轴上的变化率
  2. fy(x,y,z) 是函数在y轴上的变化率
  3. fz(x,y,z) 是函数在z轴上的变化率

方向导数就是函数在任意一个方向上的变化率。于是产生一个问题:函数沿着哪个方向变化的时候能够取得最大值?

求解:函数 u=f(x,y,z) 在沿着向量 l⃗  的方向导数为 

φuφl=φuφxcosα+φuφycosβ+φuφzcosγ=(φuφx,φuφy,φuφz)(cosα,cosβ,cosγ)=g⃗ k⃗ =|g⃗ ||k⃗ |cosθ

其中 θ 表示向量k和向量g的夹角,已知向量k的模为1,所以当 θ=0 ,即向量k和向量g的方向一致时,方向导数取得最大值,最大为 |g⃗ |

因此,梯度是一个矢量,它表示函数沿着该矢量的方向导数能够取得最大值,最大值为该矢量的模。 

grad={φuφx,φuφy,φuφz}

梯度下降

设损失函数为 

L=xyi(wxi+b)

沿着梯度的方向,函数可以取得极大值,因此梯度下降的公式为 

wwαφLφw=w+ηxyixibbαφLφb=b+ηxyi

其中的 η 表示步长,或者叫学习率,用来表示每次更新的程度。

缺点:每次迭代都需要计算全部的数据集,当数据集非常大的时候,计算效率就非常低。

随机梯度下降

即不是一次使用所有误分类点,而是随机选择一个误分类点进行梯度下降。

已知梯度下降公式为 

gradL={φLφw,φLφb}={xyixi,xyi}

不再使用全部的误分类点,而是随机选择一个误分类点的子集对wb进行更新,极端的情况就是每次只选择一个误分类点。

ww+ηyixibb+ηyi

这样,由于算法会由于在初始时选择初始值的不同,或者选择误分类点的顺序上的不同,导致最后得到的模型也会不一样。可以证明,误分类的次数是有限的,当训练数据集线性可分的时候,学习算法是收敛的。但当数据集线性不可分的时候,算法就不收敛了,迭代结果会发生震荡。

牛顿法与凸优化

其实就是泰勒公式的二阶展开。 

f(x)=f(x0)+f(x0)(xx0)+12f′′(x0)(xx0)2

求函数 f(x) 的极小值点,就是求函数 f(x)=0 的点,带入到上面的展开式,得到 
f(x)=f(x0)+f′′(x0)(xx0)=0

移项后得到 
x=x0f(x0)f′′(x0)

因此得到迭代公式 
xn+1=xnf(xn)f′′(xn)

x 表示的是高维向量时,可将公式表示为 
Xn+1=Xnf(Xn)2f(Xn)

使用牛顿法使得目标函数能够二次收敛,相比梯度下降法有更快的收敛速度。然而,牛顿法需要计算目标函数的二阶偏导数,且要求目标Hesse矩阵正定,难度比较大。

拟牛顿法

由于牛顿法需要计算目标函数的二阶偏导,且要求目标Hesse矩阵正定,难度比较大,因此有了拟合牛顿法。不过,不会用! 
http://en.wikipedia.org/wiki/Quasi-Newton_method

你可能感兴趣的:(最速下降法,牛顿法,梯度下降法,泰勒公式,拟牛顿)