机器学习 -- 梯度下降
机器学习 – 梯度下降
文章目录
一,梯度下降概念
正规方程求解的缺点
之前利用正规方程求解的W是最优解的原因是MSE这个损失函数是凸函数。但是,机器学习的损失函数并非都是凸函数,设置导数为0会得到很多个极值,不能确定唯一解,MSE还有一个问题,当数据量和特征较多时,矩阵计算量太大.
1.假设损失函数是这样的,利用正规方程求解导数为0会得到很多个极值,不能确定唯一解
使用正规方程 W = ( X T X ) − 1 X T y W=(X^TX)^{-1}X^Ty W=(XTX)−1XTy 求解要求X的特征维度 ( x 1 , x 2 , x 3 . . . ) (x_1,x_2,x_3...) (x1,x2,x3...) 不能太多,逆矩阵运算时间复杂度为 O ( n 3 ) O(n^3) O(n3) , 也就是说如果特征x的数量翻倍,计算时间就是原来的 2 3 2^3 23倍,8倍太恐怖了,假设2个特征1秒,4个特征8秒,8个特征64秒,16个特征512秒,而往往现实生活中的特征非常多,尤其是大模型,运行时间太长了
所以 正规方程求出最优解并不是机器学习和深度学习常用的手段,梯度下降算法更常用
什么是梯度下降
假设你在一个陌生星球的山地上,你想找到一个谷底,那么肯定是想沿着向下的坡行走,如果想尽快的走到谷底,那么肯定是要沿着最陡峭的坡下山。每走一步,都找到这里位置最陡峭的下坡走下一步,这就是梯度下降。
在这个比喻中,梯度就像是山上的坡度,告诉我们在当前位置上地势变化最快的方向。为了尽快走向谷底,我们需要沿着最陡峭的坡向下行走,而梯度下降算法正是这样的方法。
每走一步,我们都找到当前位置最陡峭的下坡方向,然后朝着该方向迈进一小步。这样,我们就在梯度的指引下逐步向着谷底走去,直到到达谷底(局部或全局最优点)。
在机器学习中,梯度表示损失函数对于模型参数的偏导数。具体来说,对于每个可训练参数,梯度告诉我们在当前参数值下,沿着每个参数方向变化时,损失函数的变化率。通过计算损失函数对参数的梯度,梯度下降算法能够根据梯度的信息来调整参数,朝着减少损失的方向更新模型,从而逐步优化模型,使得模型性能更好。
在 e ˉ = 1 n ∑ i = 1 n x i 2 w 2 − 2 n ∑ i = 1 n x i y i w + 1 n ∑ i = 1 n y i 2 \bar e={\frac{1}{n}}\sum_{i=1}^{n}x_{i}^{2}w^{2}-{\frac{2}{n}}\sum_{i=1}^{n} x_{i}y_{i}w+{\frac{1}{n}}\sum_{i=1}^{n} y_{i}^{2} eˉ=n1∑i=1nxi2w2−n2∑i=1nxiyiw+n1∑i=1nyi2 这个一元二次方程中,损失函数对于参数 w 的梯度就是关于 w 点的切线斜率。梯度下降算法会根据该斜率的信息来调整参数 w,使得损失函数逐步减小,从而找到使得损失最小化的参数值,优化模型的性能。
梯度下降法(Gradient Descent)是一个算法,但不是像多元线性回归那样是一个具体做回归任务的算法,而是一个非常通用的优化算法来帮助一些机器学习算法求解出最优解,所谓的通用就是很多机器学习算法都是用梯度下降,甚至深度学习也是用它来求解最优解。
所有优化算法的目的都是期望以最快的速度把模型参数W求解出来,梯度下降法就是一种经典常用的优化算法。
二,梯度下降步骤

梯度下降流程就是“猜"正确答案的过程:
1、Random随机数生成初始W,随机生成一组成正太分布的数值 w 0 , w 1 , w 2 . . . . w n w_0,w_1,w_2....w_n w0,w1,w2....wn,这个随机是成正太分布的(高斯说的)
2、求梯度g,梯度代表曲线某点上的切线的斜率,沿着切线往下就相当于沿着坡度最陡峭的方向下降.
3、if g < 0,w变大,if g >0,w变小(目标左边是斜率为负右边为正 )
4、判断是否收敛,如果收敛跳出迭代,如果没有达到收敛,回第2步再次执行2~4步收敛的判断标准是:随着迭代进行查看损失函数Loss的值,变化非常微小甚至不再改变,即认为达到收敛
三,梯度下降公式

随机给一个w初始值,然后就不停的修改它,直到达到抛物线最下面附近,比如
w=0.2
w=w-0.01*w为0.2时的梯度(导数) 假设算出来是 0.24
w=w-0.01*w为0.24时的梯度(导数) 假设算出来是 0.33
w=w-0.01*w为0.33时的梯度(导数) 假设算出来是 0.51
w=w-0.01*w为0.51时的梯度(导数) 假设算出来是 0.56
w=w-0.01*w为0.56时的梯度(导数) 假设算出来是 0.58
w=w-0.01*w为0.58时的梯度(导数) 假设算出来是 0.62
就这样一直更新下去,会在真实值附近,我们可以控制更新的次数
四,学习率
根据我们上面讲的梯度下降公式,我们知道α是学习率,设置大的学习率α;每次调整的幅度就大,设置小的学习率α;每次调整的幅度就小,然而如果步子迈的太大也会有问题! 学习率大,可能一下子迈过了,到另一边去了(从曲线左半边跳到右半边),继续梯度下降又迈回来,使得来来回回震荡。步子太小呢,就像蜗牛一步步往前挪,也会使得整体迭代次数增加。
学习率的设置是门一门学问,一般我们会把它设置成一个小数,0.1、0.01、0.001、0.0001,都是常见的设定数值(然后根据情况调整)。一般情况下学习率在整体迭代过程中是不变,但是也可以设置成随着迭代次数增多学习率逐渐变小,因为越靠近山谷我们就可以步子迈小点,可以更精准的走入最低点,同时防止走过。还有一些深度学习的优化算法会自己控制调整学习率这个值
五,自己实现梯度下降
自己用代码亲自实现一遍梯度下降,便于更好地理解API
import matplotlib.pyplot as plt
import numpy as np
def model(x,w):
return w*x
def loss(w):
return 10*(w ** 2)-15.9*w+6.5
def gradient(w):
return 20*w-15.9
# 梯度下降
w =np.random.randint(-10,10)
print("初始化的w:",w)
# alpha = 0.01
# w=w-alpha*gradient(w)
# print("梯度下降后的w:",w)
# for i in range(1000):
# w=w-alpha*gradient(w)
# print(f"训练次数{i}/1000损失值:{loss(w)}")
t0,t1=1,1000
arr=np.array([])
for i in range(100):
alpha = t0/(t1+i)
w=w-alpha*gradient(w)
arr=np.append(arr,w)
print(f"训练次数{i}/1000损失值:{loss(w)}")
plt.plot(np.linspace(-10,10,100),loss(np.linspace(-10,10,100)))
print(arr)
plt.scatter(arr,loss(arr),c="red")
plt.show()


六,sklearn梯度下降
梯度下降API常用有三种:
批量梯度下降BGD(Batch Gradient Descent)
小批量梯度下降MBGD(Mini-BatchGradient Descent)
随机梯度下降SGD(Stochastic Gradient Descent)
-
三种梯度下降的不同
-
Batch Gradient Descent (BGD): 在这种情况下,每一次迭代都会使用全部的训练样本计算梯度来更新权重。这意味着每一步梯度更新都是基于整个数据集的平均梯度。这种方法的优点是每次更新的方向是最准确的,但缺点是计算量大且速度慢,尤其是在大数据集上。
-
Mini-Batch Gradient Descent (MBGD): 这种方法介于批量梯度下降和随机梯度下降之间。它不是用全部样本也不是只用一个样本,而是每次迭代从数据集中随机抽取一小部分样本(例如,从500个样本中选取32个),然后基于这一小批样本的平均梯度来更新权重。这种方法在准确性和计算效率之间取得了一个平衡。
-
Stochastic Gradient Descent (SGD): 在随机梯度下降中,每次迭代仅使用随机单个样本(或有时称为“例子”)来计算梯度并更新权重。这种方法能够更快地收敛,但由于每次更新都基于单个样本,所以会导致权重更新路径不稳定。
6.批量梯度下降BGD
批量梯度下降是一种用于机器学习和深度学习中的优化算法,它用于最小化损失函数(目标函数)。批量梯度下降使用整个训练数据集来计算梯度并更新模型参数。
原理
批量梯度下降的基本思想是在每个迭代步骤中使用所有训练样本来计算损失函数的梯度,并据此更新模型参数。这使得更新方向更加准确,因为它是基于整个数据集的梯度,而不是像随机梯度下降那样仅基于单个样本。
更新规则
假设我们有一个包含 ( m ) 个训练样本的数据集 { ( x ( i ) , y ( i ) ) } i = 1 m \{(x^{(i)}, y^{(i)})\}_{i=1}^{m} {(x(i),y(i))}i=1m,其中 $ x^{(i)} 是输入特征, 是输入特征, 是输入特征, y^{(i)} $ 是对应的标签。我们的目标是最小化损失函数 $J(\theta) $ 相对于模型参数 $ \theta $ 的值。
损失函数可以定义为:
批量梯度下降的更新规则为:
特点
- 准确性:由于使用了所有训练样本,所以得到的梯度是最准确的,这有助于找到全局最小值。
- 计算成本:每次更新都需要遍历整个数据集,因此计算量较大,特别是在数据集很大的情况下。
- 收敛速度:虽然每一步的更新都是准确的,但由于计算成本较高,实际收敛到最小值的速度可能不如其他方法快。
- 内存需求:需要在内存中存储整个数据集,对于大型数据集来说可能成为一个问题。
使用场景
- 小数据集:当数据集较小时,批量梯度下降是一个不错的选择,因为它能保证较好的收敛性和准确性。
- 不需要实时更新:如果模型不需要实时更新,例如在离线训练场景下,批量梯度下降是一个合理的选择。
实现注意事项
- 选择合适的学习率:选择合适的学习率对于快速且稳定的收敛至关重要。如果学习率太小,收敛速度会很慢;如果太大,则可能会导致不收敛。
- 数据预处理:对数据进行标准化或归一化,可以提高批量梯度下降的效率。
- 监控损失函数:定期检查损失函数的变化趋势,确保算法正常工作并朝着正确的方向前进。
import numpy as np
# 定义线性回归模型
def linear_regression(X, y, theta, alpha, iterations):
"""
X: 输入特征矩阵 (m x n)
y: 标签向量 (m x 1)
theta: 模型参数向量 (n x 1)
alpha: 学习率
iterations: 迭代次数
"""
m = X.shape[0] # 样本数量
for _ in range(iterations):
# 计算预测值
predictions = X.dot(theta)
# 计算误差
error = predictions - y
# 计算梯度
gradient = (1/m) * X.T.dot(error)
# 更新参数
theta = theta - alpha * gradient
return theta
# 示例数据
X = np.array([[1, 2], [1, 3], [1, 4], [1, 5]]) # 输入特征矩阵,包含一列偏置项
y = np.array([[6], [7], [8], [9]]) # 标签向量
# 初始化参数
theta = np.zeros((X.shape[1], 1)) # 参数向量,包含偏置项
# 设置学习率和迭代次数
alpha = 0.01
iterations = 1000
# 训练模型
theta = linear_regression(X, y, theta, alpha, iterations)
print("训练后的参数:", theta)
训练后的参数: [[2.52989544]
[1.38360253]]
6.2 随机梯度下降
随机梯度下降(Stochastic Gradient Descent, SGD)是一种常用的优化算法,在机器学习和深度学习领域中广泛应用。与批量梯度下降(BGD)和小批量梯度下降(MBGD)相比,SGD 每一步更新参数时仅使用单个训练样本,这使得它更加灵活且计算效率更高,特别是在处理大规模数据集时。
基本步骤
-
初始化参数:
- 选择一个初始点作为参数向量 θ \theta θ的初始值。
-
选择样本:
- 随机选取一个训练样本$ (x^{(i)}, y^{(i)})$。
-
计算梯度:
- 使用所选样本 ( x ( i ) , y ( i ) ) (x^{(i)}, y^{(i)}) (x(i),y(i))来近似计算损失函数 $J(\theta) $的梯度 ∇ J ( θ ) \nabla J(\theta) ∇J(θ)。
-
更新参数:
- 根据梯度的方向来更新参数 θ \theta θ。更新公式为:
θ : = θ − α ⋅ ∇ J ( θ ) \theta := \theta - \alpha \cdot \nabla J(\theta) θ:=θ−α⋅∇J(θ) - 其中 α \alpha α 是学习率,决定了每次迭代时参数更新的步长。
- 根据梯度的方向来更新参数 θ \theta θ。更新公式为:
-
重复步骤 2 到 4:
- 对所有的训练样本重复此过程,直到完成一个完整的 epoch(即所有样本都被访问过一次)。
-
重复多个 epoch:
- 重复上述过程,直到满足某个停止条件,比如达到最大迭代次数或者梯度足够小。
-
输出结果:
- 输出最小化损失函数后的最优参数 θ ∗ \theta^* θ∗。
数学公式
假设我们有一个包含 (m) 个样本的数据集 { ( x ( i ) , y ( i ) ) } i = 1 m \{(x^{(i)}, y^{(i)})\}_{i=1}^m {(x(i),y(i))}i=1m,其中 x ( i ) x^{(i)} x(i) 是第 i i i 个样本的特征向量, y ( i ) y^{(i)} y(i) 是对应的标签。对于线性回归问题,损失函数 J ( θ ) J(\theta) J(θ) 可以定义为均方误差 (Mean Squared Error, MSE):
J ( θ ) = 1 2 ( h θ ( x ( i ) ) − y ( i ) ) 2 J(\theta) = \frac{1}{2}(h_\theta(x^{(i)}) - y^{(i)})^2 J(θ)=21(hθ(x(i))−y(i))2
其中 h θ ( x ( i ) ) = θ T x ( i ) h_\theta(x^{(i)}) = \theta^T x^{(i)} hθ(x(i))=θTx(i)是模型对第 i i i个样本的预测值。
梯度 ∇ J ( θ ) \nabla J(\theta) ∇J(θ) 对于每个参数 θ j \theta_j θj 的偏导数可以表示为:
∂ J ( θ ) ∂ θ j = ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \frac{\partial J(\theta)}{\partial \theta_j} = (h_\theta(x^{(i)}) - y^{(i)})x_j^{(i)} ∂θj∂J(θ)=(hθ(x(i))−y(i))xj(i)
更新规则
参数 θ \theta θ 的更新规则为:
θ j : = θ j − α ⋅ ∂ J ( θ ) ∂ θ j \theta_j := \theta_j - \alpha \cdot \frac{\partial J(\theta)}{\partial \theta_j} θj:=θj−α⋅∂θj∂J(θ)
注意事项
- 学习率 α \alpha α: 需要适当设置,太大会导致算法不收敛,太小则收敛速度慢。
- 随机性: 每次迭代都从训练集中随机选择一个样本,这有助于避免陷入局部最小值。
- 停止条件: 可以是达到预定的最大迭代次数,或者梯度的范数小于某个阈值。
随机梯度下降的一个关键优势在于它能够快速地进行迭代并适应较大的数据集。然而,由于每次只使用一个样本进行更新,梯度估计可能较为嘈杂,这可能导致更新过程中出现较大的波动。在实际应用中,可以通过减少学习率(例如采用学习率衰减策略)来解决这个问题。
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error
# 加载 California Housing 数据集
# data= fetch_california_housing()
# print(data.data.shape)
# print(data.feature_names)
# print(data.target.shape)
x,y = fetch_california_housing(return_X_y=True)
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=22)
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)
model = SGDRegressor(loss="squared_error",max_iter=10000,eta0=0.0001,learning_rate="constant")
model.fit(x_train,y_train)
print("模型参数:",model.coef_)
print("模型截距:",model.intercept_)
print("模型结果:",model.score(x_test,y_test))
#模型评估
score = model.score(x_test,y_test)
print("准确率:",score)
y_pred = model.predict(x_test)
mean_squared_error = mean_squared_error(y_test,y_pred)
print("均方误差:",mean_squared_error)
模型参数: [ 0.83478275 0.13166326 -0.23171733 0.30341868 -0.00137909 -0.02851386
-0.77552372 -0.75408718]
模型截距: [2.06725254]
模型结果: 0.6085706931266146
准确率: 0.6085706931266146
均方误差: 0.5086444729435525
6.3小批量梯度下降
小批量梯度下降是一种介于批量梯度下降(BGD)与随机梯度下降(SGD)之间的优化算法,它结合了两者的优点,在机器学习和深度学习中被广泛使用。
原理
小批量梯度下降的基本思想是在每个迭代步骤中使用一小部分(即“小批量”)训练样本来计算损失函数的梯度,并据此更新模型参数。这样做的好处在于能够减少计算资源的需求,同时保持一定程度的梯度准确性。
特点
- 计算效率:相比于批量梯度下降,小批量梯度下降每次更新只需要处理一部分数据,减少了计算成本。
- 梯度估计:相比于随机梯度下降,小批量梯度下降提供了更准确的梯度估计,这有助于更稳定地接近最小值。
- 内存需求:相比批量梯度下降,小批量梯度下降降低了内存需求,但仍然比随机梯度下降要高。
- 收敛速度与稳定性:小批量梯度下降能够在保持较快的收敛速度的同时,维持相对较高的稳定性。
使用场景
- 中等规模数据集:当数据集大小适中时,小批量梯度下降是一个很好的折衷方案,既能够高效处理数据,又能够保持良好的收敛性。
- 在线学习:在数据流式到达的场景下,小批量梯度下降可以有效地处理新到来的数据批次。
- 分布式环境:在分布式计算环境中,小批量梯度下降可以更容易地在多台机器上并行执行。
实现注意事项
- 选择合适的批量大小:批量大小的选择对性能有很大影响。较大的批量可以减少迭代次数,但计算成本增加;较小的批量则相反。
- 选择合适的学习率:选择合适的学习率对于快速且稳定的收敛至关重要。如果学习率太小,收敛速度会很慢;如果太大,则可能会导致不收敛。
- 数据预处理:对数据进行标准化或归一化,可以提高小批量梯度下降的效率。
- 监控损失函数:定期检查损失函数的变化趋势,确保算法正常工作并朝着正确的方向前进。
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import SGDRegressor
from sklearn.metrics import mean_squared_error
import time
import numpy as np
import os
import joblib
import math
#如果模型在本地有就加载出来继续训练(因为保存的模型是曾经训练过很多次的w和b)
#如果没有就重新训练
model_path = os.path.join(os.path.dirname(__file__), 'model','mbgd_model.pkl')
transfer_path = os.path.join(os.path.dirname(__file__), 'model','mbgd_transfer.pkl')
# print(model_path)
def train():
#加载模型
model=None
transfer=None
if os.path.exists(model_path):
model = joblib.load(model_path)
transfer = joblib.load(transfer_path)
else:
model = SGDRegressor(max_iter=100, eta0=0.01, penalty=None, learning_rate='constant')
transfer = StandardScaler()
#加载数据
x,y = fetch_california_housing(return_X_y=True)
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=42)
#标准化
transfer.fit_transform(x_train)
transfer.transform(x_test)
#训练
epochs = 10
batch_size = 32
for epoch in range(epochs):
indexs =np.arange(len(x_train))
np.random.shuffle(indexs)
# print(indexs)
# exit()
start_time = time.time()
for batch in range(math.ceil(len(x_train)/batch_size)):
start = batch * batch_size
end = min((batch+1) * batch_size,len(x_train))
index = indexs[start:end]
x_batch = x_train[index]
y_batch = y_train[index]
#训练这32条数据(本来是32条数据计算损失函数 ,进行梯度下降)
model.partial_fit(x_batch,y_batch)
y_pred=model.predict(x_test)
mse = mean_squared_error(y_test,y_pred)
# print(f"第{batch}批次训练完成,均方误差为:{mse}")
score = model.score(x_test,y_test)
print(f"训练轮次:{epoch}/{epochs} score:{score} time:{time.time()-start_time:.2f}s")
#保存模型
joblib.dump(model,model_path)
joblib.dump(transfer,transfer_path)
# def detecet():
# model = joblib.load(model_path)
# transfer = joblib.load(transfer_path)
# x_ture
if __name__ == '__main__':
train()

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)