12-梯度与梯度下降算法(末尾有图)

求梯度

先回忆求梯度的公式:

假如 L = f ( x , y , z ) L = f(x,y,z) L=f(x,y,z)
( ∂ L ∂ x , ∂ L ∂ y , ∂ L ∂ z . . . ) (\frac{\partial L}{\partial x},\frac{\partial L}{\partial y},\frac{\partial L}{\partial z}...) (xL,yL,zL...)
分别对x, y, z 求导:
∂ L ∂ x = f ( x + h , y , z ) − f ( x − h , y , z ) 2 h \frac{\partial L}{\partial x}= \frac{f(x+h,y,z)-f(x-h,y,z)}{2h} xL=2hf(x+h,y,z)f(xh,y,z)

∂ L ∂ y = f ( x , y + h , z ) − f ( x , y − h , z ) 2 h ∂ L ∂ z = f ( x , y , z + h ) − f ( x , y , z − h ) 2 h \frac{\partial L}{\partial y}= \frac{f(x,y+h,z)-f(x,y-h,z)}{2h} \\\frac{\partial L}{\partial z}= \frac{f(x,y,z+h)-f(x,y,z-h)}{2h} \\ yL=2hf(x,y+h,z)f(x,yh,z)zL=2hf(x,y,z+h)f(x,y,zh)

接下来用python实现如何求梯度:

  1. 首先定义一个 L 函数,假设为:f(x,y,z)=x**2 + y**2 + z**2
# 这里 x是个数组,可以代表x, y, z, ...
f = lambda x: np.sum(x**2) 
  1. 接下来开始求fx0 处的梯度,我们希望有一个函数,只要传入参数f, x0就能返回所要的结果,因此定义一个函数:numerical_gradient(f, x0)
def numerical_gradient(f, x0):
    h = 1e-4
    
    #产生一个与x0维度(与变量x维度一样)相同的数组,作为最后要返回的梯度
    grad = np.zeros_like(x0)  
    
    for i in range(x0.size):
        temp_x = x[i]
        x[i] = temp_x + h 
        fx1 = f(x)           #求 f(x+h)
        
        x[i] = temp_x - h
        fx2 = f(x)           #求 f(x-h)
        
        grad[i] = (fx1 - fx2)/(2 * h)
        x[i] = temp_x         # 还原值 
       
    return grad        #返回求得的梯度

代码说明:

  1. 第7行的 x.size可以得到 x的维度,所以循环是为了求多个维度的导数值
  2. 第11行,每一个维度都需要求 x[i]+h ,x[i]-h, 再求完梯度后必须要把x值恢复到最初的状态,但 x[i]+h ,x[i]-h 会导致丢失x[i] 的初值,所以第8行使用了中间变量保存初值。

接下来就可以求梯度了,例如求 f 在 (1,3,5)点处的梯度:

x0 = np.array([1.0,3.0,5.0])  #注意必须要用浮点数
print(numerical_gradient(f, x0))

#输出:
[ 2.  6. 10.]

求最小值——梯度下降法

根据梯度的含义可知,梯度就是一个向量,它的方向指向函数增加最快的地方,因此,它的负方向就指向函数下降的地方,由此引出了梯度下降算法,首先确定一个初始点,然后在该点求梯度,接着沿着梯度的方向前进一小段距离,然后继续求梯度,继续沿梯度方向前进;如此循环往复,直到下降到最低点。

用公式表示为:
x 1 = x 1 − η ∂ f ∂ x 1 x 2 = x 2 − η ∂ f ∂ x 2 . . . x n = x n − η ∂ f ∂ x n x_1 = x_1 - \eta \frac{\partial f}{\partial x_1}\\x_2 = x_2 - \eta \frac{\partial f}{\partial x_2}\\.\\.\\.\\x_n = x_n - \eta \frac{\partial f}{\partial x_n} x1=x1ηx1fx2=x2ηx2f...xn=xnηxnf
n表示函数 f的维度, η \eta η 表示学习率,实际上就是用来控制每次前进一小段距离的长度,不能太小,不然要计算梯度的次数太多,耗费时间;也不能太大,避免跨过了最值点的位置。

根据上面的公式,用python代码实现梯度下降算法如下:

def gradient_descent(f, x0, lr=0.01, step_num=100):
    x = x0
    for i in range(step_num):
        x -= lr * numerical_gradient(f, x)  
    
    return x

代码说明:

  • x0表示最初的位置
  • lr(即 learn)表示学习率
  • step_num表示要前进的次数,越大当然可以越接近最值位置,但循环次数太多就要耗费时间了

接下来求一下 f=x**2 + y**2 取得最小值时应该在哪个位置。

gradient_descent(f, np.array(10.0, 10.0)) #注意用浮点数

#输出:
array([1.32619556, 1.32619556])

我们知道,在 (0,0) 位置 f 会取到最小值,现在求得的在大约 (1.3,1.3)的位置,说明点已经从最初的(10,10)的位置前进到了 (1.3,1.3)的位置。这个结果离实际最低点还有一段距离,说明前进的次数(即循环次数)不够,或者每次前进距离(即学习率)太短了。

把循环次数提高到500次看看:

gradient_descent(f, np.array(10.0, 10.0),step_num=500)

#输出
array([0.00041024, 0.00041024]) 

这个结果已经很接近了

再看看提高学习率到0.1,但循环次数不变的情况:

gradient_descent(f, np.array(10.0, 10.0),lr=0.1)

#输出
array([2.03703598e-09, 2.03703598e-09])

这个值更小了,再将学习率提高到 0.99看看:

gradient_descent(f, np.array(10.0, 10.0),lr=0.99)

#输出
array([1.32619556, 1.32619556])

结果变大了,这说明学习率不能设的太大


弄个可视化图看看前进的过程:

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.sans-serif'] = 'FangSong'

def gradient_descent(f, x0, lr=0.01, step_num=100):
    x = x0
    fig = plt.figure()
    ax = fig.add_subplot(111)
    for i in range(step_num):
        x -= lr * numerical_gradient(f, x)
        if i % 10 == 0 and i!=0:
            ax.scatter(x[0], x[1], s=2000/i)    #s表示绘制点的面积
    plt.xlabel('x轴',fontdict=dict(fontsize=15))
    plt.ylabel('y轴',fontdict=dict(fontsize=15))
    plt.show()
    return x

gradient_descent(y, np.array([10.,10.]),lr=0.01, step_num=500)

注意从右上角往左下角移动,可以看到越接近结果的地方每次前进的距离越短,说明梯度值变小了

在这里插入图片描述

posted @ 2020-08-01 14:56  aJream  阅读(101)  评论(0编辑  收藏  举报