梯度下降引入

导数

汽车在10分钟内行驶3公里。我们可以计算其平均速度,然而,在10分钟内,汽车可能并不总是匀速行驶,因此,如果我们想求汽车在某一时刻的瞬时速度,就可以将时间进行划分成若干段(1分钟,1秒钟等)。随着时间段颗粒度的无限缩短,就能够求解出汽车在某一时刻的瞬时速度了。
导数体现的是某个瞬间的变化量,例如,汽车在某一个时刻的瞬时速度。我们可以将时间(x)与距离(y)表示为函数:
y=f(x)y = f(x)
而瞬时速度(导数)则可以体现为在一段时间内,行驶的路程与时间的商:
f(x)=df(x)dx=limΔx0f(x+Δx)f(x)Δxf'(x) = \frac{df(x)}{dx} = \lim_{\Delta x \to 0}\frac{f(x + \Delta x) - f(x)}{\Delta x}
实际上,这就是y的增量与x增量的比值而已(x的微小变化会带来y的多少变化)。在函数图像上,可以表示为曲线的割线,而导数体现为瞬时变化,即当Δx0\Delta x \to 0时的变化,这也就是曲线的切线,即曲线的斜率(tan)。

几何上的表现:割线变切线,两个点不断逼近。

数值微分原理

数值微分(numerical differentiation),是使用数值方法近似求解函数导数。然而,我们需要注意:

  • 在数学上可以表示趋近于0的数,但是在计算机中却不能。
  • 我们使用函数求解的是x+Δxx + \Delta x之间的斜率,而并非函数在xx处的斜率(因为第一点原因)。
  • 为了减小误差,我们可以计算点xx左右两侧的差分(中心差分)。
    19. 梯度下降
    19. 梯度下降
    逼近 y=x^2 2x 导数为2
    数值微分 why不准确;因为数值微分 趋近于0 但毕竟不是0。
    所以数值微分求解 ,数学上 可以表示过,程序上无法表现无限趋近于0。

我们实际要的时割线的斜率
怎么解决
中心的方式表示 除以2倍的,程序如下:
19. 梯度下降

练习: 画出导数切线

使用数值微分求解y=x2+2x+1y = x ^ 2 + 2x + 1在x=10点的导数,并画出该点的切线。
y=f(x)y = f(x)在点(x0,y0)(x_0, y_0)处的切线方程为:
yy0=f(x0)(xx0)y - y_0 = f'(x_0)(x - x_0)

import numpy as np
import matplotlib.pylab as plt

# 使用数值微分求导数值。
# 参数:f 函数
# x 自变量,求哪一点的导数值。
def numerical_diff(f, x):
    # 定义一个很小的增量值。0.0001
    h = 1e-4
    # 使用中心差分求解近似导数值。
    return (f(x + h) - f(x - h)) / (2 * h)

# 定义原函数的方程
def function(x):
    return x ** 2 + 2 * x + 1

# 切线方程
def tangent_line(f, x):
    d = numerical_diff(f, x)
    y = f(x) - d * x
    return lambda t: d * t + y
     
x = np.arange(0.0, 20.0, 0.1)
y = function(x)
plt.xlabel("x")
plt.ylabel("f(x)")

tf = tangent_line(function, 10)
y2 = tf(x)

plt.plot(x, y)
plt.plot(x, y2)
plt.show()

# print(numerical_diff(function, 10))

19. 梯度下降

偏导数

当函数具有多个变量时,保持其他变量不变(固定为某个值),而针对其中一个变量求导数,称为偏导数。
对于函数f(x0,x1,xn)f(x_0, x_1, …… x_n),对x0x_0求偏导时,因为其他变量都是固定不变的,因此,可以将其他变量都看成是常数。

设计多个自变量,对那个自变量求偏导,其他的就保持为常数。
19. 梯度下降

梯度概念

梯度是一个向量,表示函数在某一点处的方向导数
f(x1,x2,...)=(f(x1,x2,...)x1,f(x1,x2,...)x2,...)\bigtriangledown f(x1, x2, ...)=(\frac{\partial f(x1, x2, ...)}{\partial x1}, \frac{\partial f(x1, x2, ...)}{\partial x2}, ...)
由此可知,当函数是一维函数时,梯度就是导数。

通过梯度求解极值

梯度具有如下的特征:

  • 函数在某一点,在该点梯度的方向变化最快。
  • 沿着梯度的方向,函数上升最快。
  • 逆着梯度的方向,函数下降最快。

因此,我们可以通过梯度指引的方向,进而求解函数的极值。过程为:

  1. 设定一个初始坐标点。
  2. 求解该坐标点的梯度值。
  3. 根据梯度值指定的方向,前进一段距离,更新坐标值。
  4. 重复步骤2-3,直到迭代到指定的次数,或者连续迭代两次的y值小于指定的阈值为止。

根据求解极值的不同(极大值还是极小值),我们可以分为梯度上升或者梯度下降。在机器学习领域中,梯度下降的应用会更多。

说明:梯度的方向不一定指向极值,但是,沿着梯度的方向更新可以让函数的值朝着极值靠近。
19. 梯度下降
19. 梯度下降
19. 梯度下降
19. 梯度下降

一维梯度下降

程序:使用梯度下降求解方程y=x22x+1y=x^{2} - 2x + 1的最小值。
观察学习率对梯度下降的影响。学习率不是越大越好,也不是越小越好。

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"]=False

# 定义原函数方程。
def f(x):
    return x ** 2 - 2 * x + 1

# 定义梯度方程(导函数方程)
def gradient(x):
    return 2 * x - 2

# 定义初始值,即梯度下降最开始的迭代点。
x = 10
# 保存每次调整后,x的值以及x所对应的y值。即调整过程中,x与y的移动轨迹。
x_list = []
y_list = []
# 定义学习率(每次进行调整的幅度系数)
# 关于学习率的设置:学习率要设置得当,既不是越大越好,也不是越小越好。
# 如果学习率设置过小,则每次更新的非常缓慢,需要迭代很多次才能到极值附近。
# 如果学习率设置过大,则会出现震荡发散的情况,导致跳过最优解。
eta = 1.5

# 进行迭代
for i in range(30):
    # 将每次调整的值加入到列表中。
    x_list.append(x)
    y_list.append(f(x))
    # 根据梯度值来调整x,使得调整的x,能够令f(x)的值更小。
    x -= eta * gradient(x)
# 输出更新的值(更新的轨迹)
print(y_list)
print(x_list)

19. 梯度下降

%matplotlib qt
x = np.linspace(-9, 11, 200)
y = x ** 2 - 2 * x + 1
plt.plot(x, y)
# title中也支持Latex公式。
plt.title("函数$y=x^{2}-2x+1$的图像")
plt.plot(x_list, y_list, "ro--")
plt.show()

二维梯度下降

程序:使用梯度下降求解方程y=0.2(x1+x2)20.3x1x2+0.4y = 0.2(x1 + x2) ^ {2} - 0.3x1x2 + 0.4的最小值。

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib qt

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"]=False

# 定义原函数
def f(x1, x2):
    return 0.2 * (x1 + x2) ** 2 - 0.3 * x1 * x2 + 0.4

# 针对x1求梯度值。
def gradient_x1(x1, x2):
    return 0.4 * (x1 + x2) - 0.3 * x2

# 针对x2求梯度值。
def gradient_x2(x1, x2):
    return 0.4 * (x1 + x2) - 0.3 * x1

# 定义学习率
alpha = 0.5
# 定义列表,存放x1,x2与y的移动轨迹。
x1_list = []
x2_list = []
y_list = []

# 定义初始点。
x1, x2 = 4.8, 4.5

for i in range(50):
    # 使用列表加入x1,x2,与y的轨迹信息。
    x1_list.append(x1)
    x2_list.append(x2)
    y_list.append(f(x1, x2))
    # 根据梯度值调整每个自变量。
    x1 -= alpha * gradient_x1(x1, x2)
    x2 -= alpha * gradient_x2(x1, x2)

print(x1_list)
print(x2_list)
print(y_list)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190117165301915.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDQyMzY5,size_16,color_FFFFFF,t_70)
X1 = np.arange(-5, 5, 0.1)
X2 = np.arange(-5, 5, 0.1)
X1, X2 = np.meshgrid(X1, X2)
Y = np.array([X1.ravel(), X2.ravel()]).T
Y = f(Y[:, 0], Y[:, 1])
Y = Y.reshape(X1.shape)

fig = plt.figure()
ax = Axes3D(fig)
# 绘制方程曲面。
surf = ax.plot_surface(X1, X2, Y, rstride=5, cstride=5, cmap="rainbow")
# 绘制x1,x2,y的移动轨迹。
ax.plot(x1_list, x2_list, y_list, 'bo--')
# 指定为哪一个图形生成颜色条。
fig.colorbar(surf)
plt.title("函数$y = 0.2(x1 + x2) ^ {2} - 0.3x1x2 + 0.4$")
plt.show()
plt.scatter(x1_list, x2_list, c="r")
# m = plt.contourf(X1, X2, Y, 10)
# 画出等高线。
# 第4个参数(level):用来指定等高线的数量与位置。
# 直观的讲,该值越大,则等高线越密,否则越稀疏。
# m = plt.contour(X1, X2, Y, 25)

# 绘制填充的等高线。
m = plt.contourf(X1, X2, Y, 15)
# 在等高线上绘制x1与x2的移动轨迹。
plt.scatter(x1_list, x2_list, c="r")
plt.colorbar(m)

更新

对于损失函数:
J(w)=12i=1m(y(i)wTx(i))2J(w) = \frac{1}{2}\sum_{i=1}^{m}(y ^ {(i)} - w ^ {T}x ^ {(i)}) ^ {2}
我们可以使用梯度下降的方式,不断去调整权重w,进而去减小损失函数J(w)的值。经过不断迭代,最终求得最优的权重w,使得损失函数的值最小(近似最小)。
调整方式为:
wj=wjηJ(w)wjw_j = w_j - \eta\frac{\partial J(w)}{\partial w_j}
我们这里先单独对一个样本求梯度来演示,所有样本,只需要分别对每个式子进行求梯度,最后将每个求梯度的结果求和即可。

19. 梯度下降

练习:使用梯度下降求解之前的线性回归问题。

X, y, coef = make_regression(n_samples=1000, n_features=10, coef=True, bias=5.5, random_state=0, noise=10)

提示:
定义一个数组w,含有元素的数量与生成的特征数量一致。
定义一个偏置b。

梯度下降,需要自变量具有一个初始值,这里可以将w与b全部设置为初始值0,或者很小的数值。
自定义一个合适的学习了eta值。

再来一层循环【定义对所有的样本迭代的次数】
对X与y进行遍历,每次取出一个样本的数据(x)与对应的标签(target)【循环】
y_hat = w与每一个样本数据x进行对位相乘再相加, 再加上b。
w = w + eta * (y - y_hat)

相关文章:

  • 2021-05-31
  • 2022-12-23
  • 2021-09-16
  • 2022-12-23
  • 2021-04-19
  • 2021-05-01
猜你喜欢
  • 2022-12-23
  • 2021-05-25
  • 2022-03-01
  • 2021-11-12
  • 2022-02-27
  • 2021-06-25
相关资源
相似解决方案