1、梯度下降算法简介
梯度下降算法是一种求解最优化的算法。其中心思想是沿着目标函数梯度的方向更新参数值以希望达到目标函数的最小值。
梯度表示函数在某一点上变化最快的方向,所以沿着梯度的反方向,可以逐步逼近函数的最小值。在每一次迭代中,根据当前的参数值计算函数的梯度,然后按照一定的步长长(学习率)更新参数值。
2、形象化理解
假设这样一个场景:一个人被困在山上,需要从山上下来( 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走然后每走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷。
3、重要概念
- 目标优化函数
我们希望最小化的函数,通常表示为 f(x)。 - 损失函数
衡量目标函数与实际值之间的差距,通常表示为 L(x),例如:MSE。 - 梯度
损失函数关于参数 x 的偏导数,表示为 ∇L(x)。 - 学习率/步长
控制参数更新速度的超参数。
4、梯度下降算法基本原理(线性回归举例)
θ i : = θ i − α ∂ L ( θ 0 , θ 1 ) ∂ θ j ( f o r j = 1 a n d j = 0 ) \theta_i := \theta_i - \alpha \frac{\partial L(\theta_0,\theta_1)}{\partial \theta_j }\\ (for \;j =1 \;and \;j=0) θi:=θi−α∂θj∂L(θ0,θ1)(forj=1andj=0)
设置初始参数值和超参数值,对上式不断的迭代更新,直到收敛或到一定的条件停止。
其中:
- L ( θ 0 , θ 1 ) 为损失函数,包含两个参数 θ 0 和 θ 1 L(\theta_0,\theta_1)为损失函数,包含两个参数\theta_0和\theta_1 L(θ0,θ1)为损失函数,包含两个参数θ0和θ1
- ∂ L ( θ 0 , θ 1 ) ∂ θ j 为损失函数对 θ j 的梯度 \frac{\partial L(\theta_0,\theta_1)}{\partial \theta_j }为损失函数对\theta_j的梯度 ∂θj∂L(θ0,θ1)为损失函数对θj的梯度
- α 为学习效率 / 步长 \alpha为学习效率/步长 α为学习效率/步长
5、梯度下降算法实战线性回归
首先,生成所需的数据样本,真实直线为 y = 2 x + 5 y =2x+5 y=2x+5,然后再加入随机噪声,并保存为a.txt.
import numpy as np
import os
#np.random.seed(10000)
#获取当前路径os.getcwd()
path = os.getcwd()
#np.random.normal(loc=0.0, scale=1.0, size=None)
#loc:均值
#scale:标准差
#size:输出形式/维度
err = np.random.normal(0.01,1,(1,100))
arr_x = np.random.normal(loc= 0.01,scale= 1.0,size=(1,100))
arr_y = arr_x*2+5+err
#x,y矩阵以行(axis = 0行/1列)的形式拼接
data = np.concatenate((arr_x, arr_y), axis=0)
np.savetxt('a.txt',data.T)
先让我们看看数据点什么样子
定义目标函数
f
(
x
)
=
a
x
+
b
f(x)=ax+b
f(x)=ax+b
定义损失函数
L
(
a
,
b
)
=
1
2
n
∑
i
=
1
n
(
f
(
x
i
)
−
y
i
)
其中,
n
=
100
;
L(a,b) = \frac{1}{2n}\sum_{i=1}^n(f(x_i)-y_i)\\ 其中,n=100;
L(a,b)=2n1i=1∑n(f(xi)−yi)其中,n=100;
接下来定义每个参数更新的方式
a
:
=
a
−
α
∗
∂
L
(
a
,
b
)
∂
a
a:=a-\alpha*\frac{\partial L(a,b)}{\partial a }
a:=a−α∗∂a∂L(a,b)
b
:
=
b
−
α
∗
∂
L
(
a
,
b
)
∂
b
b:=b-\alpha*\frac{\partial L(a,b)}{\partial b }
b:=b−α∗∂b∂L(a,b)
其中
∂
L
(
a
,
b
)
∂
a
=
1
n
∑
i
=
1
n
(
a
x
i
+
b
−
y
i
)
∗
x
i
∂
L
(
a
,
b
)
∂
b
=
1
n
∑
i
=
1
n
(
a
x
i
+
b
−
y
i
)
\frac{\partial L(a,b)}{\partial a } = \frac{1}{n}\sum_{i=1}^n(ax_i+b-y_i)*x_i\\ \frac{\partial L(a,b)}{\partial b } = \frac{1}{n}\sum_{i=1}^n(ax_i+b-y_i)
∂a∂L(a,b)=n1i=1∑n(axi+b−yi)∗xi∂b∂L(a,b)=n1i=1∑n(axi+b−yi)
设置处置a = 0,b = 0,
α
=
0.1
\alpha=0.1
α=0.1,开始循环迭代,计算出每一步的a,b然后带入下一步中。
下面是梯度更新代码
def step_gradient(b_cur,a_cur,points,lr):
#初始化
b_grad = 0
a_grad = 0
#求N
N = float(len(points))
#求梯度
for i in range(0,len(points)):
x = points[i,0]
y = points[i,1]
b_grad += -(2/N)*(y-((a_cur*x)+b_cur))
a_grad += -(2/N)*x *(y-((a_cur*x)+b_cur))
#更新
new_b = b_cur - lr * b_grad
new_a = a_cur - lr * a_grad
return [new_b,new_a]
def gradient_desent_runner(points,start_b,start_a,lr,num_iter):
b = start_b
a = start_a
for i in range(num_iter):
b , a = step_gradient(b,a,np.array(points),lr)
return [b,a]
最后,求得拟合的直线如图所示
After 10000 iterations
b = {4.79919125} w = {1.98025295} error ={0.911736}
6、常见的梯度下降算法
1、批量梯度下降
每次计算会从所有数据样本中计算梯度,然后求平均值,作为一次迭代的梯度,对于高维数据,计算量相当大,因此,把这种梯度下降算法称之为批量梯度下降算法。
- 优点
对于凸目标函数,可以保证全局最优; 对于非凸目标函数,可以保证一个局部最优。 - 缺点
速度慢; 数据量大时不可行;
2、随机梯度下降
随机梯度下降算法是利用批量梯度下降算法每次计算所有数据的缺点,随机抽取某个数据来计算梯度作为该次迭代的梯度。
- 优点
更新频次快,优化速度更快; 一定的随机性导致有几率跳出局部最优(随机性来自于用一个样本的梯度去代替整体样本的梯度)。 - 缺点
随机性可能导致收敛复杂化,即使到达最优点仍然会进行过度优化,因此SGD得优化过程相比BGD充满动荡。
3、小批量梯度下降
小批量梯度下降算法是综合了批量梯度下降算法和随机梯度下降算法的优缺点,随机选取样本中的一部分数据,
MBGD是训练神经网络最常用的优化方法。
- 优点:
参数更新时的动荡变小,收敛过程更稳定,降低收敛难度;
7、学习率大小的问题
8、完整梯度下降代码
#简单回归问题
import numpy as np
from matplotlib import pyplot as plt
#精确解closed form solution
def compute_error_for_given_points(b,a,points):
totalError = 0
for i in range(0,len(points)):
x = points[i,0]
y = points[i,1]
totalError +=(y-(a*x+b))**2
return totalError/float(len(points))
def step_gradient(b_cur,a_cur,points,lr):
#初始化
b_grad = 0
a_grad = 0
#求N
N = float(len(points))
#求梯度
for i in range(0,len(points)):
x = points[i,0]
y = points[i,1]
b_grad += -(2/N)*(y-((a_cur*x)+b_cur))
a_grad += -(2/N)*x *(y-((a_cur*x)+b_cur))
#更新
new_b = b_cur - lr * b_grad
new_a = a_cur - lr * a_grad
return [new_b,new_a]
def gradient_desent_runner(points,start_b,start_a,lr,num_iter):
b = start_b
a = start_a
for i in range(num_iter):
b , a = step_gradient(b,a,np.array(points),lr)
return [b,a]
def run():
#修改为自己的路径
points = np.loadtxt('F:\\C-and-Python-Algorithn\\Pytorch\\execise\\liner_regression.txt')
lr = 0.1
init_b = 0
init_a = 0
num_iter = 10000
print("running.....")
[b,a] = gradient_desent_runner(points,init_b,init_a,lr,num_iter)
loss = compute_error_for_given_points(b,a,points)
print("After {%d} iterations b = {%.8f} w = {%.8f} error = {%f}"%(num_iter,b,a,loss))
show_(points,a,b)
def show_(points,w,b):
x = []
y = []
z = []
for i in range(100):
x.append(points[i][0])
y.append(points[i][1])
z.append(points[i][0]*w+b)
plt.scatter(x,y,color = 'r')
plt.plot(x,z,color= 'b')
plt.show()
if __name__ == '__main__':
#points = np.loadtxt('F:\\C-and-Python-Algorithn\\Pytorch\\execise\\liner_regression.txt')
run()