当前位置:   article > 正文

深度学习基础:梯度下降算法手撕_手撕梯度下降法

手撕梯度下降法

在深度学习中,梯度下降算法发挥着重要作用。由于深度学习模型通常具有大量的参数,因此需要一个高效的优化算法来更新这些参数。梯度下降算法通过计算损失函数关于每个参数的梯度,沿着梯度的反方向更新参数,从而实现对模型的训练。
这个步骤可以通过调用已封装好的模块自动实现,但是为了加深对于模型原理的理解,本期我们以线性拟合为例介绍梯度下降算法的数学原理和代码实现。

一、数学推导

模型构建

在回归问题中,我们想利用已知的自变量 X X X建立回归模型,
来推测因变量 Y Y Y的值:

Y = b 1 X 1 + b 2 X 2 + ⋯ + b n X n + b 0 Y=b_{1}X_{1}+b_{2}X_{2}+\cdots+b_{n}X_{n}+b_{0} Y=b1X1+b2X2++bnXn+b0

可以发现,其中关键问题就在于各个系数
B = [ b 0 , b 1 , b 2 , … , b n ] T B=[b_{0},b_{1},b_{2},\ldots,b_{n}]^{T} B=[b0,b1,b2,,bn]T的求解,于是我们进行了大量的观测,并得到了n组实验数据

{ y 1 = b 0 + b 1 x 11 + ⋯ + b p x 1 p + ε , ⋮ y i = b 0 + b 1 x i 1 + ⋯ + b p x i p + ε , ⋮ y n = b 0 + b 1 x n 1 + ⋯ + b p x n p + ε . {y1=b0+b1x11++bpx1p+ε,yi=b0+b1xi1++bpxip+ε,yn=b0+b1xn1++bpxnp+ε. y1=b0+b1x11++bpx1p+ε,yi=b0+b1xi1++bpxip+ε,yn=b0+b1xn1++bpxnp+ε.

其中 ε \varepsilon ε表示每一次观测时产生的随机误差,这一点可以联系误差理论与测量平差去理解,因此实际上我们给出的是考虑平差后的观测方程。写成线性代数的形式就是

Y = X B + E Y=XB+\mathcal{E} Y=XB+E

E ∼ N ( 0 , Σ ) \\\mathcal{E}{\sim}N(0,\Sigma) EN(0,Σ)

求解方法

为了求解 B B B,我们常采用两用算法,一种是最小二乘法(这个方法咱们以后讲),还有便是梯度下降算法。
梯度下降算法的主要思路是这样的:

  • 1.初始化参数(这里就是预设 B B B
  • 2.计算损失函数,即当前参数值下的预测值和真实值的损失
  • 3.沿着梯度方向更新参数并重新计算损失
  • 4.重复迭代直至损失值收敛到极小值处

在线性回归中,我们常用**均方误差(MSE)**作为损失函数,其公式为:

J ( θ ) = 1 2 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 . J(\theta)=\frac{1}{2m}\sum_{i=1}^{m}(h_{\theta}\big(x^{(i)}\big)-y^{(i)})^{2}. J(θ)=2m1i=1m(hθ(x(i))y(i))2.

其中

h θ ( x ( i ) ) = b 1 x 1 + b 2 x 2 + ⋯ + b n x n + b 0 h_{\theta}(x^{(i)})=b_{1}x_{1}+b_{2}x_{2}+\cdots+b_{n}x_{n}+b_{0} hθ(x(i))=b1x1+b2x2++bnxn+b0

耐心观察可以发现,MSE的实质就是对预测值和真值之间的误差求平均。
因此为了找到最合适的系数 B B B,我们要让损失函数的值最小。在第一步我们预设了系数的初始值,求出损失后需要对初始值进行修正,怎么实现呢?
在这里损失函数实质上是一个 ( n + 1 ) (n+1) (n+1)元的函数,在高等数学中我们知道函数中一点沿梯度方向存在上升/下降的最大速率,我们可以求出当前值的梯度,从而对每一个参数进行更新使得最后的总体损失逐渐下降。

梯度求解

梯度下降算法的关键在于其更新过程,公式表达如下:

θ j : = θ j − α ∂ ∂ θ j J ( θ ) . \theta_{j}:=\theta_{j}-\alpha\frac{\partial}{\partial\theta_{j}}J(\theta). θj:=θjαθjJ(θ).

其中 α \alpha α控制梯度下降算法的速率(也就是深度学习中的学习率),该算法是一个非常直观的算法,即每一次 θ \theta θ值的更新都沿着 J J J减小最快的方向。
当然,这里有一个必要的注意点:保证梯度下降算法达到全局最优解的条件是 J ( θ ) J(\theta) J(θ)必须是凹函数。
下面求解每一步 θ \theta θ的改正值:

∂ ∂ θ j J ( θ ) = ∂ ∂ θ j 1 2 m ( h θ ( x ) − y ) 2 = 1 m ( h θ ( x ) − y ) ∂ ∂ θ j ( h θ ( x ) − y ) = 1 m ( h θ ( x ) − y ) ∂ ∂ θ j ( ∑ i = 0 n θ i x i − y ) = 1 m ( h θ ( x ) − y ) x j θjJ(θ)=θj12m(hθ(x)y)2=1m(hθ(x)y)θj(hθ(x)y)=1m(hθ(x)y)θj(ni=0θixiy)=1m(hθ(x)y)xj θjJ(θ)=θj2m1(hθ(x)y)2=m1(hθ(x)y)θj(hθ(x)y)=m1(hθ(x)y)θj(i=0nθixiy)=m1(hθ(x)y)xj

所以梯度下降算法的更新准则是:

θ j : = θ j − α m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) . \theta_j:=\theta_j-\frac{\alpha}{m}(h_\theta\big(x^{(i)}\big)-y^{(i)})x_j^{(i)}. θj:=θjmα(hθ(x(i))y(i))xj(i).

其中 m m m表示观测方程个数。
以上就是全部理论推导,我们只需利用更新准则不断对参数进行调整最终求得最优解。

二、代码编写

在写代码时我们需要注意循环终止条件的设置,可以采取人为规定循环次数的方式(相当于深度学习中epoch)来终止循环。
下面是Python代码展示,此处我们只设置了 b 0 b_0 b0 b 1 b_1 b1作为演示

import numpy as np
import pandas as pd
from tqdm import tqdm
import matplotlib.pyplot as plt

def DataLoader(path):
    # 加载数据
    df = pd.read_csv(path, header=None)
    x = df.iloc[:, 0].values
    y = df.iloc[:, 1].values
    return x, y

def get_J(x, y, b1, b0):
    # 计算损失函数
    h = b1 * x + b0
    delta2 = np.square(y - h)
    J = np.mean(delta2) / 2
    return J

def get_gradients(x, y, b1, b0):
    # 计算梯度
    m = len(x)
    h = b1 * x + b0
    grad_k = np.dot(h - y, x) / m
    grad_b = np.mean(h - y)
    return grad_k, grad_b

def update(x, y, b1, b0, alpha):
    # 更新参数
    grad_k, grad_b = get_gradients(x, y, b1, b0)
    b1 -= alpha * grad_k
    b0 -= alpha * grad_b
    return b1, b0

def main():
    # 超参设置
    alpha = 0.0008
    epoch = 4000
    path = './Data_Test2/Data/data.csv'
    x, y = DataLoader(path)

    # 初始化参数
    b1 = 1
    b0 = 0.3

    # 列表存放每轮的损失,以便绘图
    l = []
    e = []
    loss = get_J(x,y,b1,b0)
    l.append(loss)
    ep = 0
    e.append(ep)

    # 梯度下降
    for i in tqdm(range(epoch)):
        e.append(i+1)
        b1, b0 = update(x, y, b1, b0, alpha)
        loss = get_J(x, y, b1, b0)
        l.append(loss)

    plt.plot(e, l)
    plt.title('Loss vs. Epoch')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.show()

if __name__ == '__main__':
    main()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

运行结果展示:

可以发现该模型于500轮左右便已经收敛,因此可以适当减少训练轮数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/975586
推荐阅读
相关标签
  

闽ICP备14008679号