问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

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

创作时间:
作者:
@小白创作中心

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

引用
CSDN
1.
https://blog.csdn.net/2301_77168269/article/details/140619767

在深度学习中,梯度下降算法发挥着重要作用。由于深度学习模型通常具有大量的参数,因此需要一个高效的优化算法来更新这些参数。梯度下降算法通过计算损失函数关于每个参数的梯度,沿着梯度的反方向更新参数,从而实现对模型的训练。

这个步骤可以通过调用已封装好的模块自动实现,但是为了加深对于模型原理的理解,本文以线性拟合为例介绍梯度下降算法的数学原理和代码实现。

一、数学推导

模型构建

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

Y = b1X1 + b2X2 + ⋯ + bnXn + b0

可以发现,其中关键问题就在于各个系数

B = [b0, b1, b2, …, bn]T

的求解,于是我们进行了大量的观测,并得到了n组实验数据

{y1 = b0 + b1x11 + ⋯ + bp x1p + ε,

yi = b0 + b1xi1 + ⋯ + bp xip + ε,

yn = b0 + b1xn1 + ⋯ + bpxnp + ε.

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

Y = XB + E

E ∼ N(0, Σ)

求解方法

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

梯度下降算法的主要思路是这样的:

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

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

J(θ) = 1/2m ∑i=1m (hθ(x(i)) − y(i))2.

其中

hθ(x(i)) = b1x1 + b2x2 + ⋯ + bnxn + b0

耐心观察可以发现,MSE的实质就是对预测值和真值之间的误差求平均。

因此为了找到最合适的系数B,我们要让损失函数的值最小。在第一步我们预设了系数的初始值,求出损失后需要对初始值进行修正,怎么实现呢?

在这里损失函数实质上是一个(n+1)元的函数,在高等数学中我们知道函数中一点沿梯度方向存在上升/下降的最大速率,我们可以求出当前值的梯度,从而对每一个参数进行更新使得最后的总体损失逐渐下降。

梯度求解

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

θj := θj − α ∂/∂θj J(θ).

其中α控制梯度下降算法的速率(也就是深度学习中的学习率),该算法是一个非常直观的算法,即每一次θ值的更新都沿着J减小最快的方向。

当然,这里有一个必要的注意点:保证梯度下降算法达到全局最优解的条件是J(θ)必须是凹函数。

下面求解每一步θ的改正值:

∂/∂θj J(θ) = ∂/∂θj 1/2m (hθ(x) − y)2
= 1/m (hθ(x) − y) ∂/∂θj (hθ(x) − y)
= 1/m (hθ(x) − y) ∂/∂θj (∑i=0n θixi − y)
= 1/m (hθ(x) − y)xj

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

θj := θj − α/m (hθ(x(i)) − y(i)) xj(i).

其中m表示观测方程个数。

以上就是全部理论推导,我们只需利用更新准则不断对参数进行调整最终求得最优解。

二、代码编写

在写代码时我们需要注意循环终止条件的设置,可以采取人为规定循环次数的方式(相当于深度学习中epoch)来终止循环。

下面是Python代码展示,此处我们只设置了b0和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()

运行结果展示:

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

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号