第8关 一招鲜吃遍_梯度下降法_人工智能课程 - 小象学院

课程目录 小象学院 - 人工智能

关注公众号【Python家庭】领取1024G整套教材交流群学习商务合作。整理分享了数套四位数培训机构的教材,现免费分享交流学习,并提供解答、交流群

你要的白嫖教程,这里可能都有喔~

恭喜你闯进了第8关,让我们继续探索机器学习的奥秘,体验算法的魔力Amazing~

经过前几关的学习,我相信你已经具备了独自打怪的能力,取经路上遇到的一些小妖小怪已经入不了你的法眼了。

梯度下降法

本关我们再学习一个大法,这个大法在机器学习项目开发过程中经常用到,什么样的大法这么厉害呢?它就是四海八荒无人不知无人不晓的梯度下降法

梯度下降法不是一个机器学习算法,是一种基于搜索的最优化方法,它的作用最小化一个损失函数

上一关我们学习的简单线性回归算法的核心目标就是使损失函数 J 的值尽可能的小,那么梯度下降法正好可以解决这个问题。

梯度下降法如此的厉害,那么它是如何最小化损失函数的呢?请听我给你慢慢讲。

函数的导数

想熟练的使用梯度下降法,首先要了解梯度下降法的原理,下面通过一幅图开始学习梯度下降法的原理。

上图表示的是一个函数f(x)在二维平面上的曲线,过曲线上某个点 画一条切线,切线与x轴的夹角是锐角α(小于90度),表示这条切线是一条上升直线,那么切线的斜率就是正数

在曲线方程中,某个点的导数就代表这个点的切线斜率,切线的斜率是正数,导数就是正数。

那么导数为正在曲线上表现出来的意义是:函数 f(x) 在 x 轴某一点 处,沿着 x 轴正方向(向右的方向)随着x的增大增大

刚才我们在曲线的右侧选取了一点画了一条切线,下面我们在曲线的左侧再选取一点画一条切线,看看效果。

过曲线上点的切线与x轴的夹角是钝角α(大于90度),表示这条切线是一条下降直线,那么切线的斜率就是负数

刚才我们说了,切线的斜率就是导数,它的斜率是负数,那么导数也是负数。

那么导数为负在曲线上表现出来的意义是:函数 f(x) 在 x 轴某一点 ​​ 处,沿着 x 轴正方向随着x的增大减小

由此,我们可以总结出一个规律

在 x 轴上某一点 处,如果该点的导数大于0,说明函数 f(x) 在 点沿 x 轴正方向随着 x 值的增大而增大;如果该点的导数小于0,说明函数 f(x) 在 点沿 x 轴正方向随着x值的增大而减小

那么,我们就得出来一个结论:导数可以代表方向,导数是正数就是函数 f(x) 增大的方向,导数是负数就是函数 f(x) 减小的方向

小助手提示:

小助手列举了一些常用的导数,以及记录下来,以备不时之需。

有了这么强大的理论支撑之后,我们可以把导数应用到损失函数求解过程中。

废话不多说,先上图,有图有真相!

现在我们要求损失函数的最小值,那么就是要找到图中曲线的最低点,我们站在上帝的视角从图中是马上就能看到曲线的最低点在什么位置,但是如果用电脑计算出曲线的最低点,它是没有上帝视角的,它只能一步一步地算。

那我们就站在上帝视角教电脑一步一步地计算,找到曲线的最低点,也就是损失函数的最小值。

求最小值的步骤

我们要把上图的计算过程,翻译成电脑能理解的分步操作,步骤如下:

1.随机选取一个,我们选取的是曲线右侧的 ​​,对应在曲线上的点是 ​​,​​ 对应的损失函数值是

2.计算曲线在 处的导数 ,由于,如果沿着 增大的方向,J(θ) J(\theta) J(θ)就会继续增大,我们要找的是最小值,所以要向减小的方向移动,那么就是向导数 的反方向移动,就要在 ​​前添加一个负号:​​

3.通过第2步的导数,我们锁定了移动的方向,移动的时候具体迈多大步子呢?也就是移动的距离是多大呢?这就需要定义一个步长 (读音:yi ta),然后用步长乘以第2步计算出来的方向:,这样电脑就知道向哪个方向移动,迈多大步子了;

4.经过第3步的移动,电脑就来到了 ​​ 点,由于 点的损失函数值还不是最小值,所以要从第2步开始继续操作,一直在第2步到第4步之间循环操作,直到达到曲线的最低点,就求得了损失函数最小值的

我们从刚才的操作步骤和上面的步骤解析图片观察到,整个过程就像一个小朋友在下楼梯,这个小朋友在不断地在寻找哪个方向是下楼的方向,往下楼方向走要迈几节楼梯,直到走到最低层。

所以我们把刚才介绍的这种求损失函数最小值的方法,称为梯度下降法

学习率

从刚才梯度下降法的操作步骤中,我们注意到了一个神秘的参数 (读音:yi ta),决定了移动的步长,也就是它决定了一个人下楼梯的速度。

在机器学习中, 是有专门的名称的,叫做学习率。它的值不是机器自己学出来的,是人工设置的,跟之前学习的k近邻算法中的k一样,也是个超参数

的取值会影响获得最优解的速度,有时候可能由于的取值不合适,最终得不到最优解,所以在计算过程中可能需要多次调整的值。

我们通过两副图看看对梯度下降法的影响:

太小,将大大减慢找到最优解的速度,会非常耗费机器资源。

太大,有可能移动的跨度很大,直接跨过极值点,最终找不到最优解。

那是不是只要选择的合适,就一定能找到最小值点呢?我用一张图来回答你这个问题。

上图中这个函数有多个极值点(极值点处的导数都为0),分别是S1、S2、S3。很明显S2是最小值点,是全局最优解。S1和S3也是极值点,但是他们不是最小值,只能算是局部最优解。

这种情况的难点是:如果把初始点定在右侧,从右往左找极值点的话,可能会先找到一个局部最优解S3,但是局部最优解并不是我们要找的最小值,需要继续搜索寻找全局最优解。电脑不具备上帝视角,不能第一眼就能看到全局最优解的位置,那电脑怎么确定它找到的极值是不是全局最优解呢?

常用的解决方法:尝试随机找几个不同的初始点,多次运行,每次的运行结果跟之前的结果比较一下,看是不是最好的解,通过逐步尝试找到一个最优解。

这种方式并不保证一定能够找到全局最优解,但是多试几次没准能够找到一个更好的解,毕竟对于一个非常复杂的函数来说,很有可能局部最优解的个数也是多个。

其实梯度下降法除了学习率是超参数外,初始点也是一个超参数,初始点位置的设置也非常重要。

可能有些同学听了刚才的介绍,觉得梯度下降法又要设置一个合适的值,又要找一个合适的初始点,还不一定能够找到全局最优解,算什么大法,哼~

那我要告诉你一个好消息,我们之前学过的线性回归算法的损失函数具有唯一的全局最优解,你可以用梯度下降大法去解决线性回归问题,一展身手!

梯度下降的内功心法我们已经掌握了,接下来我们就要通过实战来验证内功的实力!

代码实现

我们先模拟一个场景:

定义一个数组x_arr,数组里存放的是[1,8]之间等间距的121个点(包含开头的1和结尾的8),在二维平面上用一条曲线把这121个点串联起来。

如果是在二维平面上画曲线,x轴上的每个点 都要对应Y轴上的一个​​,我们假设x与y之间满足一种关系

我们知道这是一个以x=4.5为对称轴的对称曲线,x=4.5的点是该曲线的最低点,也就是函数y的最小值点。

AI_8_0_1

import numpy as np

# 取[1,8]之间等间距的121个点,包含开头的1和结尾的8
x_arr = np.linspace(1,8,121)
print(x_arr)
[1.         1.05833333 1.11666667 1.175      1.23333333 1.29166667
 1.35       1.40833333 1.46666667 1.525      1.58333333 1.64166667
 1.7        1.75833333 1.81666667 1.875      1.93333333 1.99166667
 2.05       2.10833333 2.16666667 2.225      2.28333333 2.34166667
 2.4        2.45833333 2.51666667 2.575      2.63333333 2.69166667
 2.75       2.80833333 2.86666667 2.925      2.98333333 3.04166667
 3.1        3.15833333 3.21666667 3.275      3.33333333 3.39166667
 3.45       3.50833333 3.56666667 3.625      3.68333333 3.74166667
 3.8        3.85833333 3.91666667 3.975      4.03333333 4.09166667
 4.15       4.20833333 4.26666667 4.325      4.38333333 4.44166667
 4.5        4.55833333 4.61666667 4.675      4.73333333 4.79166667
 4.85       4.90833333 4.96666667 5.025      5.08333333 5.14166667
 5.2        5.25833333 5.31666667 5.375      5.43333333 5.49166667
 5.55       5.60833333 5.66666667 5.725      5.78333333 5.84166667
 5.9        5.95833333 6.01666667 6.075      6.13333333 6.19166667
 6.25       6.30833333 6.36666667 6.425      6.48333333 6.54166667
 6.6        6.65833333 6.71666667 6.775      6.83333333 6.89166667
 6.95       7.00833333 7.06666667 7.125      7.18333333 7.24166667
 7.3        7.35833333 7.41666667 7.475      7.53333333 7.59166667
 7.65       7.70833333 7.76666667 7.825      7.88333333 7.94166667
 8.        ]

AI_8_0_2

import numpy as np

# 取[1,8]之间等间距的121个点,包含开头的1和结尾的8
x_arr = np.linspace(1,8,121)

# 根据x与y之间的关系,计算出数组x_arr中每个元素对应的y值组成的数组
y_arr = (x_arr - 4.5) ** 2 + 1
print(y_arr)

在二维平面上画出这条曲线

AI_8_0_3

import matplotlib.pyplot as plt 
import numpy as np
%matplotlib inline

x_arr = np.linspace(1,8,121)
y_arr = (x_arr - 4.5) ** 2 + 1

# 画出曲线图
plt.plot(x_arr,y_arr)
plt.show()

我们站在上帝视角观察这个曲线,知道它的最低点是在x=4.5的这个位置,但是电脑不知道,电脑只知道有条曲线是,对于电脑来说 x 是个未知数,它要通过梯度下降法找到这条曲线的最低点,也就是找到 x=4.5 这个点。

使用Python实现曲线方程

AI_8_0_4_定义曲线方程函数

# 定义曲线方程函数
def y_curve(x):
    return (x - 4.5)**2 + 1

曲线方程 y对x求导 :

AI_8_0_5_定义曲线方程求导函数

# 定义曲线方程求导函数
def d_y_curve(x):
    return 2 * (x - 4.5)

接下来实现梯度下降法,在写代码之前,我们先回顾几个知识点

  • 学习率(移动的步长)是超参数,需要人工设置一个初始值;

  • 需要选择一个初始起点,是个超参数;

  • 导数的正方向是曲线增大的方向;

  • 步长 * 导数(梯度)表示向梯度增大的方向移动,也就是向曲线值增大的方向移动,由于要寻找曲线的最小值点,所以要向反方向(曲线减小)的方向移动。

拓展一个小知识点,在一会的代码中会用到。在使用梯度下降法移动的过程中,需要不断地验证当前到达的点是不是最小值点。一个曲线上的某个点是极值点,那么这个点的导数就等于0

在编程具体实现的时候,有些问题需要注意:有可能由于 学习率(步长)的值设置的不合适,或者在求导的时候有计算机针对浮点数有计算精度误差的问题,使得最终求得的最小值点达不到这个点的导数等于0的情况,也就是说最终求得的最小值点有可能是非常接近最小值点,但是没有完完全全与最小值点重合。

这个问题的解决方法就是定义一个变量值趋近于0的变量 (读音:epsilon) = 0.0000000001,这个值可以随意定义。在对计算得到的极值点进行验证的时候,可以与 进行对比,具体的验证方法在接下来的代码中实现。

梯度下降法代码实现

AI_8_0_6

import numpy as np

# 梯度下降法
'''
    参数:
    x_init:初始点值
    eta_init:学习率(步长)
    min_x_history:存储所有移动过程中到过的点的x值,用于画图
'''
def gradient_descent(x_init,eta_init,min_x_history):
    # 最小值点,选设置一个起始点值
    min_x = x_init 

为了方便观察使用梯度下降法在曲线上移动的过程,我们定义一个画图函数,整个移动过程在图形上刻画出来。

AI_8_0_7

测试代码

万事俱备只欠东风了,下面开始测试。

AI_8_0_8

from sys import path
path.append(r"../data/course_util")
from ai_course_8_1 import *
%matplotlib inline

x_arr = np.linspace(1,8,121)
y_arr = (x_arr - 4.5) ** 2 + 1

x_init = 2.0 # 起始点

eta_init = 0.1 # 学习率(步长)

min_x_history = [x_init] # 存储所有移动过程中到过的点的x值,用于画图

# 使用梯度下降法求最小值
gradient_descent(x_init,eta_init,min_x_history)

# 在曲线上画出梯度下降过程中移动的轨迹
show_picture(x_arr,y_arr,min_x_history)

我们从梯度下降的移动轨迹,和最终求得的最小值点 (4.499891109642585,1.00000001185711) 几乎就是(4.5,1)这个点,所以验证了使用梯度下降成功找到了最小值点。

今天我们先到这里,拜拜~

 

联系我们,一起学Python吧

分享Python实战代码,入门资料,进阶资料,基础语法,爬虫,数据分析,web网站,机器学习,深度学习等等。


​关注公众号「Python家庭领取1024G整套教材交流群学习商务合作

猜你喜欢

转载自blog.csdn.net/qq_34409973/article/details/114935506
今日推荐