梯度下降引入
导数
汽车在10分钟内行驶3公里。我们可以计算其平均速度,然而,在10分钟内,汽车可能并不总是匀速行驶,因此,如果我们想求汽车在某一时刻的瞬时速度,就可以将时间进行划分成若干段(1分钟,1秒钟等)。随着时间段颗粒度的无限缩短,就能够求解出汽车在某一时刻的瞬时速度了。
导数体现的是某个瞬间的变化量,例如,汽车在某一个时刻的瞬时速度。我们可以将时间(x)与距离(y)表示为函数:
而瞬时速度(导数)则可以体现为在一段时间内,行驶的路程与时间的商:
实际上,这就是y的增量与x增量的比值而已(x的微小变化会带来y的多少变化)。在函数图像上,可以表示为曲线的割线,而导数体现为瞬时变化,即当时的变化,这也就是曲线的切线,即曲线的斜率(tan)。
几何上的表现:割线变切线,两个点不断逼近。
数值微分原理
数值微分(numerical differentiation),是使用数值方法近似求解函数导数。然而,我们需要注意:
- 在数学上可以表示趋近于0的数,但是在计算机中却不能。
- 我们使用函数求解的是之间的斜率,而并非函数在处的斜率(因为第一点原因)。
- 为了减小误差,我们可以计算点左右两侧的差分(中心差分)。
逼近 y=x^2 2x 导数为2
数值微分 why不准确;因为数值微分 趋近于0 但毕竟不是0。
所以数值微分求解 ,数学上 可以表示过,程序上无法表现无限趋近于0。
我们实际要的时割线的斜率
怎么解决
中心的方式表示 除以2倍的,程序如下:
练习: 画出导数切线
使用数值微分求解在x=10点的导数,并画出该点的切线。
在点处的切线方程为:
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))
偏导数
当函数具有多个变量时,保持其他变量不变(固定为某个值),而针对其中一个变量求导数,称为偏导数。
对于函数,对求偏导时,因为其他变量都是固定不变的,因此,可以将其他变量都看成是常数。
设计多个自变量,对那个自变量求偏导,其他的就保持为常数。
梯度概念
梯度是一个向量,表示函数在某一点处的方向导数。
由此可知,当函数是一维函数时,梯度就是导数。
通过梯度求解极值
梯度具有如下的特征:
- 函数在某一点,在该点梯度的方向变化最快。
- 沿着梯度的方向,函数上升最快。
- 逆着梯度的方向,函数下降最快。
因此,我们可以通过梯度指引的方向,进而求解函数的极值。过程为:
- 设定一个初始坐标点。
- 求解该坐标点的梯度值。
- 根据梯度值指定的方向,前进一段距离,更新坐标值。
- 重复步骤2-3,直到迭代到指定的次数,或者连续迭代两次的y值小于指定的阈值为止。
根据求解极值的不同(极大值还是极小值),我们可以分为梯度上升或者梯度下降。在机器学习领域中,梯度下降的应用会更多。
说明:梯度的方向不一定指向极值,但是,沿着梯度的方向更新可以让函数的值朝着极值靠近。
一维梯度下降
程序:使用梯度下降求解方程的最小值。
观察学习率对梯度下降的影响。学习率不是越大越好,也不是越小越好。
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)
%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()
二维梯度下降
程序:使用梯度下降求解方程的最小值。
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)

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)
更新
对于损失函数:
我们可以使用梯度下降的方式,不断去调整权重w,进而去减小损失函数J(w)的值。经过不断迭代,最终求得最优的权重w,使得损失函数的值最小(近似最小)。
调整方式为:
我们这里先单独对一个样本求梯度来演示,所有样本,只需要分别对每个式子进行求梯度,最后将每个求梯度的结果求和即可。
练习:使用梯度下降求解之前的线性回归问题。
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)