梯度下降法和牛顿法计算开根号
本文将介绍如何不调包,只能使用加减乘除法实现对根号x的求解。主要介绍梯度下降和牛顿法者两种方法,并给出 C++ 实现。
梯度下降法
思路/步骤
- 转化问题,将 x\sqrt{x}x 的求解转化为最小化目标函数:L(t)L(t)L(t),L(t)=(t2−x)L(t)=(t^2-x)L(t)=(t2−x) ,当 LLL 趋近于 0 时,ttt 就是我们想要的结果;
- 迭代寻找使得 LLL 变小的 ttt ,
- 最终得到足够小的 LLL 时的 ttt ,即使得 L→0L\rightarrow 0L→0, 得到结果 ttt
- 求解 LLL 的极小值,就是导数为 0 的点
如何迭代
OK,现在的问题就是要如何进行迭代,从而得到尽可能小的 LLL,为此,我们要使得随着每一次迭代中 ttt 的变化,LLL 都朝着更小的方向变化一个合适的步长。
确定如何迭代,无非就是要确定每次迭代的方向和步长。
最自然的想法,我们使得 ttt 朝政府两个方向都移动一个很小的步长,然后比一比,看哪个的 LLL 更小了,就向哪个方向移动。即:
- 若 L(t+Δt)<L(t)L(t+\Delta t)<L(t)L(t+Δt)<L(t) ,则 t1=t+Δtt_1=t+\Delta tt1=t+Δt;
- 若 L(t−Δt)<L(t)L(t-\Delta t)<L(t)L(t−Δt)<L(t) ,则 t1=t−Δtt_1=t-\Delta tt1=t−Δt;
注意这里的 Δt\Delta tΔt 应当是一个大于零的无穷小数,即 0+0^+0+ 。
我们接下来再对上面的式子进行一点变化:
- 若 L(t+Δt)−L(t)<0L(t+\Delta t)-L(t)<0L(t+Δt)−L(t)<0 ,则 t1=t+Δtt_1=t+\Delta tt1=t+Δt;
- 若 L(t)−L(t−Δt)>0L(t)-L(t-\Delta t)>0L(t)−L(t−Δt)>0 ,则 t1=t−Δtt_1=t-\Delta tt1=t−Δt;
将这两个式子写在一起:
t1=t−L(t+Δt)−L(t)∣L(t+Δt)−L(t)∣⋅Δtt_1=t-\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}\cdot \Delta t t1=t−∣L(t+Δt)−L(t)∣L(t+Δt)−L(t)⋅Δt
这里的 L(t+Δt)−L(t)∣L(t+Δt)−L(t)∣\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}∣L(t+Δt)−L(t)∣L(t+Δt)−L(t) 用来指示正负号。再进行一点变形:
t1=t−L(t+Δt)−L(t)∣L(t+Δt)−L(t)∣⋅Δt=t−L(t+Δt)−L(t)Δt∣L(t+Δt)−L(t)Δt∣⋅Δt=t−L(t+Δt)ΔtΔt∣L(t+Δt)−L(t)Δt∣=t−αL′(t),α=Δt∣L(t+Δt)−L(t)Δt∣→0+,L′(t)=L(t+Δt)−L(t)Δt\begin{align} t_1&=t-\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}\cdot \Delta t\\ &=t-\frac{\frac{L(t+\Delta t)-L(t)}{\Delta t}}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\cdot \Delta t\\ &=t-\frac{L(t+\Delta t)}{\Delta t}\frac{\Delta t}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\\ &=t-\alpha L'(t), \ \ \ \alpha=\frac{\Delta t}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\rightarrow 0^+,\ \ \ L'(t)=\frac{L(t+\Delta t)-L(t)}{\Delta t} \end{align} t1=t−∣L(t+Δt)−L(t)∣L(t+Δt)−L(t)⋅Δt=t−∣ΔtL(t+Δt)−L(t)∣ΔtL(t+Δt)−L(t)⋅Δt=t−ΔtL(t+Δt)∣ΔtL(t+Δt)−L(t)∣Δt=t−αL′(t), α=∣ΔtL(t+Δt)−L(t)∣Δt→0+, L′(t)=ΔtL(t+Δt)−L(t)
-
当a取无穷小时,虽然一定保证下降,但效率太慢
-
日常设计的很多函数,可以允许使用相对大一些的步长,比如 α=0.01\alpha = 0.01α=0.01。理由是,若步长大了,出现跳过合适位置,使得 L(t1)>L(t0)L(t1) > L(t0)L(t1)>L(t0)。再下一个时刻,依旧可能跳回来使得 L(t2)<L(t1)L(t2) < L(t1)L(t2)<L(t1)
-
大的步长不能保证一定收敛,但是大部分时候是可以很好的工作,也因此,步长 α\alphaα,我们称之为学习率,通常会给一个相对小的数字,但不会太小。
-
总之,学习率一般需要在不同的模型任务中手动调试。
代码
float sqrt_grad_decent(float x) {float t = x / 2;float L = (t * t - x) * (t * t - x);float alpha = 0.001;while ( L > 1e-5 ) {float delta = 2 * (t * t - x) * 2 * t;t = t - alpha * delta;L = (t * t - x) * (t * t - x);printf("t=%f\n", t);}return t;
}
总结
-
梯度下降法是通过观察局部,决定如何调整的算法。如果函数具有多个极值,则可能陷入局部极值,此时初始点的选择直接影响收敛结果
-
大的步长在一定程度上可能跨过局部极值,但也可能造成震荡导致不收敛
-
步长的选择,需要根据函数的特性来找到合适取值,若导数特别大时,则步长取小,导数小时,步长可大。否则很容易造成收敛问题
-
存在一类算法,可以在一定范围内搜索一个合适步长,使得每一次迭代更加稳定
牛顿法1
梯度下降法常用语求解函数极小值的情况,而牛顿法常用于求解函数零点的情况,即 L=0L=0L=0 时方程的根。
思路/步骤
- 转化问题,将求解 x\sqrt{x}x 转换为求解 L(t)=t2−x=0L(t)=t^2-x=0L(t)=t2−x=0 时的根,即函数的零点
- 迭代寻找 ttt
如何迭代
用曲线在 t0t_0t0 处切线与 xxx 轴的交点作为 t1t_1t1 ,来逼近函数的零点。图/牛顿法
切线斜率,同样可以用导数来表示 。
考虑两个坐标系:原坐标系 o1o1o1 ,新坐标系 o2o2o2 ,其中 o2o2o2 以 o1o1o1 中的 (x1,f(x1))(x_1,f(x_1))(x1,f(x1)) 为原点。则在 o2o2o2 坐标系中,下图红色切线可表示为:
fo2(x)=f′(x1)xf_{o2}(x)=f'(x_1)x fo2(x)=f′(x1)x
则该切线与 xxx 轴交点:
fo2(x2)=f′(x1)(x2−x1)=−f(x1)f_{o2}(x_2)=f'(x_1)(x_2-x_1)=-f(x_1) fo2(x2)=f′(x1)(x2−x1)=−f(x1)
则有:
x2−x1=−f(x1)f′(x1)x2=x1−f(x1)f′(x1)x_2-x_1=-\frac{f(x_1)}{f'(x_1)}\\ x_2=x_1-\frac{f(x_1)}{f'(x_1)} x2−x1=−f′(x1)f(x1)x2=x1−f′(x1)f(x1)
代码
我们经过上一小节已经知道迭代的方法:
t1=t−L(t)L′(t)t_1=t-\frac{L(t)}{L'(t)} t1=t−L′(t)L(t)
代码:
float sqrt_newton1(float x) {float t = x / 2;float L = t * t - x;while ( abs(L) > 1e-5 ) {float dL = 2 * t;t = t - L / dL;L = t * t - x;}return t;
}
牛顿法2
思路
既然牛顿法是对函数求零点,那我们能不能对函数的导函数求零点呢?这样就可以得到函数的极值了。
与梯度下降法的目标函数 L(t)=(t2−x)L(t)=(t^2-x)L(t)=(t2−x) 是相同的,而区别在于,迭代式不同 t1=t−f′(t)f′′(t)t_1=t-\frac{f'(t)}{f''(t)}t1=t−f′′(t)f′(t),并且其中步长(学习率)为 1。
代码
float sqrt_newton2(float x) {float t = x / 2;float L = (t * t - x) * (t * t - x);while ( L > 1e-5 ) {float dL = 2 * (t * t - x) * 2 * t;float d2L = 12 * t * t - 4 * x;t = t - dL / d2L;L = (t * t - x) * (t * t - x);}return t;
}
Ref
- 牛顿法