在线性回归中,我们使用最小二乘法,能够直接计算损失函数最小值时的参数值,但是,最小二乘法有使用的限制条件,在大多数机器学习的使用场景之下,我们会选择梯度下降的方法来计算损失函数的极小值,首先梯度下降算法的目标仍然是求最小值,但和最小二乘法这种一步到位、通过解方程组直接求得最小值的方式不同,梯度下降是通过一种“迭代求解”的方式来进行最小值的求解,其整体求解过程可以粗略描述为,先随机选取一组参数初始值,然后沿着某个方向,一步一步移动到极小值点
梯度下降法的基本思想可以类比为一个下山的过程:
一个人 被困在山上,需要从山上下来 (i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走
首先,我们有一个 可微分的函数 。这个函数就代表着一座山。我们的目标就是找到 这个函数的最小值 ,也就是山底。根据之前的场景假设,最快的下山的方式就是找到当前位置最陡峭的方向,然后沿着此方向向下走,对应到函数中,就是 找到给定点的梯度 ,然后朝着梯度相反的方向,就能让函数值下降的最快。
向量求导的梯度向量形式 :
在给定具体的参数一组取值之后,我们就能计算梯度表达式的取值,该值也被称为损失函数在某组参数取值下的梯度
当前的函数是一元函数,我们只需要计算导数即可算出梯度值
def gradient_descent(df, x, alpha=0.01, iterations=100, epsilon=1e-8):
history = [x]
for i in range(iterations):
if abs(df(x)) < epsilon:
print("梯度足够小")
break
x = x - alpha * df(x)
history.append(x)
return history
import numpy as np
x = np.linspace(0,6,1000)
y = np.power(x,3) - 3*np.power(x,2)-9*x +2
yf = lambda x: np.power(x,3) - 3*np.power(x,2)-9*x +2
# 求好了导函数,作为一个函数,作用于下面的更新方法
y_d = lambda x: 3*np.power(x,2) - 6*x -9
# 参数表示x的初始值选择1,并且学习率是0.01, 迭代100次
path = gradient_descent(y_d, 1,0.01, 100)
path[-1]
path = gradient_descent(y_d, 0.5,0.01, 200)
path[-1]
可以发现迭代200次后,就触发了停止迭代的条件,说明迭代次数以及足够多而且梯度值以及不足以继续下降了。
我们还可以将迭代过程进行可视化展示:
path_x = np.array(path)
path_y = yf(path_x)
plt.plot(x, y)
plt.scatter(x[y.argmin()],y[y.argmin()])
plt.text(0.5,yf(0.5),"start")
plt.quiver(path_x[:-1], path_y[:-1], path_x[1:]-path_x[:-1],path_y[1:]-path_y[:-1], scale_units='xy',angles="xy",scale=1)
plt.show()
有了梯度计算公式之后,我们可以使用gradient_descent方法进行迭代计算,计算出x_0的值
loss_prime = lambda x:(np.power(x,2)-2)*x
path = gradient_descent(loss_prime, 1, 0.01,500)
path[-1]
输出1.414213559949299,最小值为1.4142135623730951
先读取并展示数据点:
import matplotlib.pyplot as plt
plt.scatter(data[:,0],data[:,1])
plt.show()
def linear_regression_gd_iteration(X, y, w, alpha=0.01, iterations=100, epsilon=1e-9):
history = [w]
for i in range(iterations):
gd = linearRegression_gd(X, y, w)
if np.max(np.abs(gd)< epsilon):
print("梯度过小")
break
w = w - alpha * gd
history.append(w)
return history
lr = 0.001
iterations = 10000
w0 = np.array([-7.5,-7.5]).reshape(-1,1)
path = linear_regression_gd_iteration(X, y , w0,lr,iterations)
path[-1]
def draw_line(X,y,w,lineWidth=2):
plt.scatter(X[:,0],y)
yhat = X @ w
plt.plot(X[:,0],yhat,'r-',linewidth=lineWidth)
plt.show()
draw_line(X, y, path[-1])
如果学习率调整为0.3,那么将找不到最低点,还会产生震荡**
我们将学习率改为0.01时,最后的值为path[-1] = 2.9999876804184145,已经很接近x = 3。
我们再将学习率改为0.001,此时曲线将离最低点还有一段距离:
包括我们将迭代次数改为1000次后,我们的x值将为2.999999999256501,无限接近与最低点。
假设我们有一些散点数据,需要拟合它:
train_x = data[:,0]
train_y = data[:,1]
X = np.concatenate([train_x.reshape(-1,1), np.ones_like(train_x.reshape(-1,1))], axis=1)
# 加偏置
y = train_y.reshape(-1,1)
np.linalg.lstsq(X, y, rcond=-1)
我们可以用最小二乘法来求解,求出损失最小的权值w和b,通过正规方程,得出w=0.93099006,b = -1.52679069。
回到最初的问题,正规方程或者是最小二乘法求解过程中,存在诸多的限制,接下来采用梯度下降来求解该问题;
我们使用MSE作为损失函数,则该损失函数的梯度表达式为
def linearRegression_gd(X, y, w):
"""
梯度计算公式
"""
m = X.shape[0]
gd = 2 * X.T.dot(X.dot(w)-y) / m
return gd
linearRegression_gd(X[:1,:],y[:1,:],np.array([1,1]).reshape(-1,1))
# 结果:-107.237845,-25.528548
w
维度相同的向量。X.T.dot(误差项)
是用来计算梯度的一种方式,它通过考虑所有样本的贡献来同时更新模型的所有参数。我们使用梯度下降求解出的结果为w = 0.940894,b = -1.627557 ,现在已经很接近正规方程计算出的最小值,但是还没有收敛,还需继续迭代或者更换学习率。
梯度下降用于最小化损失函数以找到模型参数的最佳估计,重点就是学习率、迭代次数和初始点的选择;
模型参数初始值会影响梯度下降的收敛速度和最终解,良好的初始化可以加速收敛过程,避免陷入局部极小值或鞍点。
精细地调整参数。
模型参数初始值会影响梯度下降的收敛速度和最终解,良好的初始化可以加速收敛过程,避免陷入局部极小值或鞍点。
策略:随机初始化,多次选择初始点位,避免一开始从不合适的点迭代。