梯度下降法的实现(超详细)

梯度下降法(深入理解+可视化)

梯度下降法(Gradient Descent)是一种常用的数值优化方法,用于寻找一个函数的最小值。在机器学习中,梯度下降法被广泛应用于训练神经网络和其他机器学习模型。
为了更好的理解梯度下降法的原理,本文首先使用了一个凹凸不平的函数(同时计算其自变量的梯度),之后进行梯度下降法的迭代过程。与此同时,为了更好的将寻找局部最小值的过程进行可视化,在此采用了动态的生成点的方式对实现梯度下降过程的可视化(分别选取了上下四分位点)。

可视化结果如下:

在本图中可以通过代码的第115行控制是否动态的的生成点,达到动态的寻找最小值的效果。
在这里插入图片描述
在这里插入图片描述

具体步骤:

1.导入所需要的包
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
2.使用一个凹凸不平的函数,目的是对梯度下降的过程进行详细的可视化,同时计算函数中参数的梯度。
def loss(x1, x2):
    #  自己找了一个凹凸不平的函数方便观察不同的起始点的收敛路径。
    #  草帽函数
    return np.sin(np.sqrt(x1 ** 2 + x2 ** 2)) * 2 + 2

def gradient(x1, x2):
    if (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2)) != 0:
        x1_grad = 2 * x1 / (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2))
    if (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2)) != 0:
        x2_grad = 2 * x2 / (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2))
    return x1_grad, x2_grad
3.梯度下降法的实现过程,具体原理如下。

在这里插入图片描述
在此将参数的变化过程,loss的变化过程,以及迭代的次数进行了保存,方便后续的可视化。对应代码实现如下:

def BGD(a):  # 梯度下降法
    change_rate = a   # 学习率
    #可设置不同的初始值观察迭代的过程
    #x1, x2 = 1.5, -1.5  # 初始值
    x1,x2=2.5,6.5 #这个点不太好
    #x1,x2=6,6
    #x1, x2 = 6, -6

    print('初始参数x1= {} '.format(x1))
    print('初始参数x2= {} '.format(x2))
    end = 10 ** (-10)  # 阈值
    times = 0  # 迭代次数
    x1_list = [x1]
    x2_list = [x2]
    time_list = [0]
    loss_list = [loss(x1, x2)]
    # 在此定义了四个列表来记录梯度变化的过程。
    while True:
        # 迭代过程
        temp1 = change_rate * gradient(x1, x2)[0]
        x1 -= temp1
        temp2 = change_rate * gradient(x1, x2)[1]
        x2 -= temp2
        # 分别将每一次的迭代结果放入对应的列表中
        x1_list.append(x1)
        x2_list.append(x2)
        time_list.append(times)
        loss_list.append(loss(x1, x2))
        times += 1
        # 梯度的变化小于阈值的时候跳出循环(两次迭代之间的差值小于该阈值)
        if abs(temp1) < end and abs(temp2) < end:
            break
    return x1_list, x2_list, loss_list, time_list
4.可视化

为了达到较好的可视化效果,在可视化点的过程中,寻找迭代过程中具有代表性的数字进行可视化(二分位数和四分位数)
代码如下:

def LookforPoint(list1,point):  # 返回最接近分位数的那个点的下标
    length1 = len(list1)
    index1 = 0
    temp = 100000
    for i in range(length1):
        if abs(list1[i] - point) < temp:
            temp = list1[i] - point
            index1 = i
    return index1

附完整代码:

# -*-coding:utf-8-*-
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np


def loss(x1, x2):
    #  自己找了一个凹凸不平的函数方便观察不同的起始点的收敛路径。
    #  草帽函数
    return np.sin(np.sqrt(x1 ** 2 + x2 ** 2)) * 2 + 2

def gradient(x1, x2):
    if (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2)) != 0:
        x1_grad = 2 * x1 / (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2))
    if (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2)) != 0:
        x2_grad = 2 * x2 / (np.sqrt(x1 ** 2 + x2 ** 2)) * np.cos(np.sqrt(x1 ** 2 + x2 ** 2))
    return x1_grad, x2_grad


def BGD(a):  # 梯度下降法
    change_rate = a   # 学习率
    #可设置不同的初始值观察迭代的过程
    #x1, x2 = 1.5, -1.5  # 初始值
    x1,x2=2.5,6.5 #这个点不太好
    #x1,x2=6,6
    #x1, x2 = 6, -6

    print('初始参数x1= {} '.format(x1))
    print('初始参数x2= {} '.format(x2))
    end = 10 ** (-10)  # 阈值
    times = 0  # 迭代次数
    x1_list = [x1]
    x2_list = [x2]
    time_list = [0]
    loss_list = [loss(x1, x2)]
    # 在此定义了四个列表来记录梯度变化的过程。
    while True:
        # 迭代过程
        temp1 = change_rate * gradient(x1, x2)[0]
        x1 -= temp1
        temp2 = change_rate * gradient(x1, x2)[1]
        x2 -= temp2
        # 分别将每一次的迭代结果放入对应的列表中
        x1_list.append(x1)
        x2_list.append(x2)
        time_list.append(times)
        loss_list.append(loss(x1, x2))
        times += 1
        # 梯度的变化小于阈值的时候跳出循环(两次迭代之间的差值小于该阈值)
        if abs(temp1) < end and abs(temp2) < end:
            break
    return x1_list, x2_list, loss_list, time_list


def LookforPoint(list1,point):  # 返回最接近分位数的那个点的下标
    length1 = len(list1)
    index1 = 0
    temp = 100000
    for i in range(length1):
        if abs(list1[i] - point) < temp:
            temp = list1[i] - point
            index1 = i
    return index1

if __name__ == '__main__':
    #可设置不同的学习率观察迭代过程
    # change_rate=[0.1,0.02,0.01]
    # for j in range(len(change_rate)):
    #     x1_list, x2_list, loss_list, time_list = BGD(change_rate[j])
    #     plt.plot(time_list, loss_list, label='change_rate='+str(change_rate[j]))
    #     plt.legend()
    # plt.show()
    a = 0.01 # 学习率
    x1_list, x2_list, loss_list, time_list = BGD(a)
    print('共迭代{}次'.format(len(time_list)))
    print('迭代后的x1={}'.format(x1_list[len(x1_list) - 1]))
    print('迭代后的x2={}'.format(x2_list[len(x2_list) - 1]))
    plt.xlabel('迭代次数')
    plt.ylabel('变化情况')
    plt.plot(time_list, x1_list,label=u'x1参数变化')
    plt.plot(time_list, loss_list,label=u"loss函数变化")
    plt.legend()    # 用来标示不同图形的文本标签图例

    # 绘制图像
    plt.rcParams['font.sans-serif'] = 'SimHei'
    plt.rcParams['axes.unicode_minus'] = False
    fig = plt.figure()
    ax = Axes3D(fig)
    xx1, xx2 = np.meshgrid(np.arange(-8, 8, 0.1), np.arange(-8, 8, 0.1))  # 网格坐标
    z = np.array(loss(xx1, xx2))
    ax.plot_surface(xx1, xx2, z, cmap='rainbow', alpha=0.5)
    ax.view_init(60, -40)  # 观察角度
    # 在此透明度设置为0.5,目的是让画出的点更加明显

    #   开始画点
    xx1, xx2 = np.array(x1_list), np.array(x2_list)
    z = np.array(loss(xx1, xx2))
    k = 0  # 标记点的标签
    length = len(xx1)

    num = loss_list[0] - loss_list[length-1]    # 损失函数最大值与最小值之差
    num1 = num/2+loss_list[length-1]  # 二分位数
    num2 = num1-num/4  # 上四分位数
    num3 = num1+num/4  # 下四分位数
    point1 = LookforPoint(loss_list, num1)  # 找到最接近二分位数的那个点
    point2 = LookforPoint(loss_list, num2)  # 找到最接近上四分位数的那个点
    point3 = LookforPoint(loss_list, num3)  # 找到最接近下四分位数的那个点

    for i in range(length):
        if i == 0 or i == point1 or i == point2 or i == point3 or i == length-1:  # 要展示的五个点
            ax.scatter(xx1[i], xx2[i], loss_list[i], marker='*', color='blue', s=50, alpha=1)
            j = [1, 2, 3, 4, 5]  # 为路径添加标签
            ax.text(xx1[i], xx2[i], loss_list[i], j[k])
            k += 1
            plt.pause(1)   # 可以实现动态的生成点的过程
    res = z[len(xx1) - 1]  # 局部最优解
    print('局部最优解是 {} '.format(res))
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.set_zlabel('f(x1,x2)')
    plt.show()


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ming__chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值