梯度下降法和牛顿法计算开根号
本文将介绍如何不调包,只能使用加减乘除法实现对根号x的求解。主要介绍梯度下降和牛顿法者两种方法,并给出 C++ 实现。
梯度下降法
思路/步骤
- 转化问题,将 x \sqrt{x} x 的求解转化为最小化目标函数: L ( t ) L(t) L(t), L ( t ) = ( t 2 − x ) L(t)=(t^2-x) L(t)=(t2−x) ,当 L L L 趋近于 0 时, t t t 就是我们想要的结果;
- 迭代寻找使得 L L L 变小的 t t t ,
- 最终得到足够小的 L L L 时的 t t t ,即使得 L → 0 L\rightarrow 0 L→0, 得到结果 t t t
- 求解 L L L 的极小值,就是导数为 0 的点
如何迭代
OK,现在的问题就是要如何进行迭代,从而得到尽可能小的 L L L,为此,我们要使得随着每一次迭代中 t t t 的变化, L L L 都朝着更小的方向变化一个合适的步长。
确定如何迭代,无非就是要确定每次迭代的方向和步长。
最自然的想法,我们使得 t t t 朝政府两个方向都移动一个很小的步长,然后比一比,看哪个的 L L L 更小了,就向哪个方向移动。即:
- 若 L ( t + Δ t ) < L ( t ) L(t+\Delta t)<L(t) L(t+Δt)<L(t) ,则 t 1 = t + Δ t t_1=t+\Delta t t1=t+Δt;
- 若 L ( t − Δ t ) < L ( t ) L(t-\Delta t)<L(t) L(t−Δt)<L(t) ,则 t 1 = t − Δ t t_1=t-\Delta t t1=t−Δt;
注意这里的 Δ t \Delta t Δt 应当是一个大于零的无穷小数,即 0 + 0^+ 0+ 。
我们接下来再对上面的式子进行一点变化:
- 若 L ( t + Δ t ) − L ( t ) < 0 L(t+\Delta t)-L(t)<0 L(t+Δt)−L(t)<0 ,则 t 1 = t + Δ t t_1=t+\Delta t t1=t+Δt;
- 若 L ( t ) − L ( t − Δ t ) > 0 L(t)-L(t-\Delta t)>0 L(t)−L(t−Δt)>0 ,则 t 1 = t − Δ t t_1=t-\Delta t t1=t−Δt;
将这两个式子写在一起:
t
1
=
t
−
L
(
t
+
Δ
t
)
−
L
(
t
)
∣
L
(
t
+
Δ
t
)
−
L
(
t
)
∣
⋅
Δ
t
t_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) 用来指示正负号。再进行一点变形:
t
1
=
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 ( t 1 ) > L ( t 0 ) L(t1) > L(t0) L(t1)>L(t0)。再下一个时刻,依旧可能跳回来使得 L ( t 2 ) < L ( t 1 ) 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 = 0 L=0 L=0 时方程的根。
思路/步骤
- 转化问题,将求解 x \sqrt{x} x 转换为求解 L ( t ) = t 2 − x = 0 L(t)=t^2-x=0 L(t)=t2−x=0 时的根,即函数的零点
- 迭代寻找 t t t
如何迭代
用曲线在 t 0 t_0 t0 处切线与 x x x 轴的交点作为 t 1 t_1 t1 ,来逼近函数的零点。图/牛顿法
切线斜率,同样可以用导数来表示 。
考虑两个坐标系:原坐标系
o
1
o1
o1 ,新坐标系
o
2
o2
o2 ,其中
o
2
o2
o2 以
o
1
o1
o1 中的
(
x
1
,
f
(
x
1
)
)
(x_1,f(x_1))
(x1,f(x1)) 为原点。则在
o
2
o2
o2 坐标系中,下图红色切线可表示为:
f
o
2
(
x
)
=
f
′
(
x
1
)
x
f_{o2}(x)=f'(x_1)x
fo2(x)=f′(x1)x
则该切线与
x
x
x 轴交点:
f
o
2
(
x
2
)
=
f
′
(
x
1
)
(
x
2
−
x
1
)
=
−
f
(
x
1
)
f_{o2}(x_2)=f'(x_1)(x_2-x_1)=-f(x_1)
fo2(x2)=f′(x1)(x2−x1)=−f(x1)
则有:
x
2
−
x
1
=
−
f
(
x
1
)
f
′
(
x
1
)
x
2
=
x
1
−
f
(
x
1
)
f
′
(
x
1
)
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)
代码
我们经过上一小节已经知道迭代的方法:
t
1
=
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 ) = ( t 2 − x ) L(t)=(t^2-x) L(t)=(t2−x) 是相同的,而区别在于,迭代式不同 t 1 = 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;
}