梯度下降算法是机器学习基础算法,假如x是数据样本的特征,y是其目标值,我们需要通过计算,求解有个目标函数,最能拟合这个函数,我们就用最简单的线性函数进行拟合,
h
θ
(
x
)
=
θ
0
+
θ
1
∗
x
h_{\theta}(x) = \theta_{0} + \theta_{1} * x
hθ(x)=θ0+θ1∗x,比如这个函数就是
y
=
5
+
2
∗
x
y=5+2*x
y=5+2∗x,现在只要能通过x与y求解出
θ
0
=
5
,
θ
1
=
2
\theta_{0}=5,\theta_{1}=2
θ0=5,θ1=2
我们用代码进行解释下
import numpy as np
max_x = 10
data_size = 10
theta_0 = 5
theta_1 = 2
def get_data():
# (在start和stop之间返回均匀间隔的数据),在1到10之间,返回10个数字
x = np.linspace(1, max_x, data_size)
# 创建一个列表,中包含10个数,他们正态分布均值为0,标准差为0.2,这一步相当是创建一些干扰项,让机器更难辨别
noise = np.random.normal(0, 0.2, len(x))
y = theta_0 + theta_1 * x + noise
return x, y
我们先要有数据,才能模拟,我们自己创建一下数据,增加干扰项后增加是识别难度,
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y = [6.66, 9.11, 11.08, 12.67, 15.12, 16.76, 18.75, 21.35, 22.77, 24.56]
我们现在就是要寻找出一条直线,能最大的拟合这个点
这个直线明显很差,我们需要重新寻找
那这个过程如何用代码体现出来呢,我们衡量一个目标是否准确,可以用最小二乘法,这个最小二乘法
1
/
2
(
h
θ
(
x
i
)
−
y
i
)
2
=
1
/
2
(
y
^
i
−
y
i
)
2
=
1
/
2
(
θ
0
+
θ
1
∗
x
i
−
y
i
)
2
1/2(h_\theta(x^i) - y^i)^2 =1/2 (\widehat{y}^{i} - y^i)^2 = 1/2(\theta_{0} + \theta_{1} * x^{i} - y^{i})^2
1/2(hθ(xi)−yi)2=1/2(y
i−yi)2=1/2(θ0+θ1∗xi−yi)2
只要这个值最小,就是最好的。
def cost_function(x, y, t0, t1):
cost_sum = 0
for i in range(len(x)):
#power(x, y) 函数,计算 x 的 y 次方。
cost_item = np.power(t0 + t1 * x[i] - y[i], 2)
cost_sum += cost_item
return cost_sum / len(x)
定义损失函数,计算平均损失值。
从这个图形中我们可以看出,当
θ
0
,
θ
1
\theta_{0} , \theta_{1}
θ0,θ1越接近 [5, 2]时其结果(偏差)越小。相反,离得越远,结果越大。
从上面这幅图中我们可以看出,代价函数在不同的位置结果大小不同。
从三维的角度来看,这就和地面的高低起伏一样。最高的地方就好像是山顶。
而我们的目标就是:从任意一点作为起点,能够快速寻找到一条路径并以此到达图形最低点(代价值最小)的位置。
而梯度下降的算法过程就和我们从山顶想要快速下山的做法是一样的。
算法描述
梯度下降算法最开始的一点就是需要确定下降的方向,即:梯度。
我们常常用
Δ
f
(
θ
)
\Delta f(\theta )
Δf(θ)来表示梯度。对于一个二维空间的曲线来说,梯度就是其切线的方向。如下图所示:
而对于更高维空间的函数来说,梯度由所有变量的偏导数决定。
[
∇
f
(
θ
)
=
(
∂
f
(
θ
)
∂
θ
1
,
∂
f
(
θ
)
∂
θ
2
,
.
.
.
,
∂
f
(
θ
)
∂
θ
n
)
]
[\nabla f({\theta}) = ( \frac{\partial f({\theta})}{\partial \theta_1} , \frac{\partial f({\theta})}{\partial \theta_2} , ... , \frac{\partial f({\theta})}{\partial \theta_n} )]
[∇f(θ)=(∂θ1∂f(θ),∂θ2∂f(θ),...,∂θn∂f(θ))]
在机器学习中,我们主要是用梯度下降算法来最小化代价函数,记做:
[ θ ∗ = a r g m i n L ( θ ) ] [\theta ^* = arg min L(\theta)] [θ∗=argminL(θ)]
其中,L是代价函数,是参数。
梯度下降算法的主体逻辑很简单,就是沿着梯度的方向一直下降,直到参数收敛为止。
记做:
[ θ i k + 1 = θ i k − λ ∇ f ( θ k ) ] [\theta ^{k + 1}_i = \theta^{k}_i - \lambda \nabla f(\theta^{k})] [θik+1=θik−λ∇f(θk)]
这里的下标i表示第i个参数。
上标k指的是第k步的计算结果,而非k次方
收敛是指函数的变化率很小。具体选择多少合适需要根据具体的项目来确定。在演示项目中我们可以选择0.01或者0.001这样的值。不同的值将影响算法的迭代次数,因为在梯度下降的最后,我们会越来越接近平坦的地方,这个时候函数的变化率也越来越小。如果选择一个很小的值,将可能导致算法迭代次数暴增。
公式中的 称作步长,也称作学习率(learning rate)。它决定了每一步往前走多远,认为它是一个类似0.01或0.001的固定值。
线性回归的梯度下降
首先,根据代价函数我们可以得到梯度向量如下:
[
∇
f
(
θ
)
=
(
∂
L
(
θ
)
∂
θ
0
,
∂
L
(
θ
)
∂
θ
1
)
=
(
2
m
∑
i
=
1
m
(
θ
0
+
θ
1
∗
x
i
−
y
i
)
,
2
m
∑
i
=
1
m
(
θ
0
+
θ
1
∗
x
i
−
y
i
)
x
i
)
]
[\nabla f({\theta}) = (\frac{\partial L(\theta)}{ \partial\theta_{0}}, \frac{ \partial L(\theta)}{ \partial\theta_{1}}) = (\frac {2}{m} \sum_{i=1}^{m}(\theta_{0} + \theta_{1} * x^{i} - y^{i}) , \frac {2}{m} \sum_{i=1}^{m}(\theta_{0} + \theta_{1} * x^{i} - y^{i}) x^{i})]
[∇f(θ)=(∂θ0∂L(θ),∂θ1∂L(θ))=(m2i=1∑m(θ0+θ1∗xi−yi),m2i=1∑m(θ0+θ1∗xi−yi)xi)]
接着,将每个偏导数带入迭代的公式中,得到:
θ
0
:
=
θ
0
−
λ
∂
L
(
θ
0
)
∂
θ
0
=
θ
0
−
2
λ
m
∑
i
=
1
m
(
θ
0
+
θ
1
∗
x
i
−
y
i
)
\theta_{0} := \theta_{0} - \lambda \frac{\partial L(\theta_{0})}{ \partial\theta_{0}} = \theta_{0} - \frac {2 \lambda }{m} \sum_{i=1}^{m}(\theta_{0} + \theta_{1} * x^{i} - y^{i})
θ0:=θ0−λ∂θ0∂L(θ0)=θ0−m2λi=1∑m(θ0+θ1∗xi−yi)
θ
1
:
=
θ
1
−
λ
∂
L
(
θ
1
)
∂
θ
1
=
θ
1
−
2
λ
m
∑
i
=
1
m
(
θ
0
+
θ
1
∗
x
i
−
y
i
)
x
i
\ \theta_{1} := \theta_{1} - \lambda \frac{\partial L(\theta_{1})}{ \partial\theta_{1}} = \theta_{1} - \frac {2 \lambda }{m} \sum_{i=1}^{m}(\theta_{0} + \theta_{1} * x^{i} - y^{i}) x^{i}
θ1:=θ1−λ∂θ1∂L(θ1)=θ1−m2λi=1∑m(θ0+θ1∗xi−yi)xi
由此就可以通过代码实现我们的梯度下降算法了
learning_rate = 0.01
def gradient_descent(x, y):
t0 = 10
t1 = 10
delta = 0.001
for times in range(1000):
sum1 = 0
sum2 = 0
for i in range(len(x)):
sum1 += (t0 + t1 * x[i] - y[i])
sum2 += (t0 + t1 * x[i] - y[i]) * x[i]
t0_ = t0 - 2 * learning_rate * sum1 / len(x)
t1_ = t1 - 2 * learning_rate * sum2 / len(x)
print('Times: {}, gradient: [{}, {}]'.format(times, t0_, t1_))
if (abs(t0 - t0_) < delta and abs(t1 - t1_) < delta):
print('Gradient descent finish')
return t0_, t1_
t0 = t0_
t1 = t1_
print('Gradient descent too many times')
return t0, t1
这段代码说明如下:
- 我们随机选择了 θ 0 θ 1 \theta_{0}\theta_{1} θ0θ1 都为10作为起点
- 设置最多迭代1000次
- 收敛的范围设为0.001
- 学习步长设为0.01
Times: 125, gradient: [4.955453758569991, 2.000005017897775]
Times: 126, gradient: [4.955309381126545, 1.9956928964532015]
Times: 127, gradient: [4.9542964317327005, 1.9855674828684156]
Times: 128, gradient: [4.9536358220657, 1.9781180992510465]
Times: 129, gradient: [4.95412496254411, 1.9788858350530971]
从输出中可以看出,算法迭代了129次就收敛了。这时的结果[4.95412496254411, 1.9788858350530971],这已经比较接近目标值 [5, 2]了。如果需要更高的精度,可以将delta的值调的更小,当然,此时会需要更多的迭代次数。
高维扩展
虽然我们举的例子是二维的,但是对于更高维的情况也是类似的。同样是根据迭代的公式进行运算即可:
[ θ i = θ i − λ ∂ L ( θ ) ∂ θ i = θ i − 2 λ m ∑ i = 1 m ( h θ ( x k ) − y k ) x i k ] [\theta_{i} = \theta_{i} - \lambda \frac {\partial L(\theta)}{\partial \theta_i} = \theta_{i} - \frac{2\lambda}{m} \sum_{i=1}^{m}(h_\theta(x^{k})-y^k)x_i^k] [θi=θi−λ∂θi∂L(θ)=θi−m2λi=1∑m(hθ(xk)−yk)xik]
这里的下标i表示第i个参数,上标k表示第k个数据。