写在最前面的话:
我个人觉得可视化在理解一些概念的时候会有很大的帮助,所以我尝试了很久才构造出这个符合我想法的函数,这个函数的可视化效果也很直观,应该能对大家理解 梯度下降+动量 有些帮助
1 效果展示
1.1 纯梯度下降3D效果图(epoch=1000)
1.2 梯度下降+动量3D效果图(epoch=122)
1.3 函数的等高线效果图
2 代码讲解
2.1 三个函数(表达式都一样,功能不一样)
求偏导,进行梯度下降: zz=xx**2+yy**2+yy**3+xx*yy
计算梯度下降的点的坐标: zp=xp**2+yp**2+yp**3+xp*yp
画3D函数图: z=x**2+y**2+y**3+x*y
2.2 简单梯度下降+动量算法(只要注释两行就是纯梯度下降)
lr=0.01 #学习率
xx,yy = sympy.symbols('x y')
zz=xx**2+yy**2+yy**3+xx*yy #设置函数
dzdx=sympy.diff(zz, xx) #函数求偏导
dzdy=sympy.diff(zz, yy)
x_val=4 #设置初始点
y_val=5
last_gx=0 #保存上一次的梯度
last_gy=0
result=np.array([[4,5]]) #记录梯度下降的点
for epoch in range(122): #迭代次数
grad_x=dzdx.subs({xx: x_val, yy: y_val}) #给偏导函数赋值计算x方向上的梯度
grad_y=dzdy.subs({xx: x_val, yy: y_val})
x_val=x_val-(grad_x+last_gx*0.9)*lr #根据梯度更新
y_val=y_val-(grad_y+last_gy*0.9)*lr
#注释下面两行就是单纯梯度下降的算法
last_gx=grad_x
last_gy=grad_y
result=np.append(result,[[x_val,y_val]],axis=0) #记录新的梯度下降的点
#print(x_val,y_val)
#print("-------------------------------------------")
print(grad_x,grad_y) #打印最终的梯度
#print(result)
2.3 画图(函数3D曲面图+梯度下降3D曲线图+函数等高线图)
#画函数3D曲面图
fig = plt.figure(figsize=(8, 6), dpi=100) #开一个窗口进行显示
ax = Axes3D(fig)
u=np.linspace(-6,6,600)
x,y=np.meshgrid(u,u) #x,y生成网格,画3D曲面图需要
print(x.shape)
z=x**2+y**2+y**3+x*y
ax.plot_surface(x,y,z,rstride=6,cstride=6,cmap=plt.get_cmap('rainbow'),alpha=0.5) #画3D曲面图,alpha调透明度
#画梯度下降3D曲线图
xp=result[:,0] #读取梯度下降点的x坐标
yp=result[:,1]
zp=xp**2+yp**2+yp**3+xp*yp #计算梯度下降的点的z坐标
print(zp.shape)
ax.plot(xp, yp, zp, 'o', c='r',linestyle="-", linewidth=2.5) #画3D曲线图
plt.title("3D-picture")# 设置标题
#画函数等高线图
plt.figure(figsize=(8, 6), dpi=80)
plt.contourf(x, y, z, 8, alpha=0.75, cmap=plt.cm.hot)
C = plt.contour(x, y, z, 10, colors = 'black', linewidth = 0.5)
plt.clabel(C, inline = True, fontsize = 10)# 显示各等高线的数据标签
plt.show()
2.4 完整代码(spyer编译通过)
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
import sympy
lr=0.01 #学习率
xx,yy = sympy.symbols('x y')
zz=xx**2+yy**2+yy**3+xx*yy #设置函数
dzdx=sympy.diff(zz, xx) #函数求偏导
dzdy=sympy.diff(zz, yy)
x_val=4 #设置初始点
y_val=5
last_gx=0 #保存上一次的梯度
last_gy=0
result=np.array([[4,5]]) #记录梯度下降的点
for epoch in range(122): #迭代次数
grad_x=dzdx.subs({xx: x_val, yy: y_val}) #给偏导函数赋值计算x方向上的梯度
grad_y=dzdy.subs({xx: x_val, yy: y_val})
x_val=x_val-(grad_x+last_gx*0.9)*lr #根据梯度更新
y_val=y_val-(grad_y+last_gy*0.9)*lr
#注释下面两行就是单纯梯度下降的算法
last_gx=grad_x
last_gy=grad_y
result=np.append(result,[[x_val,y_val]],axis=0) #记录新的梯度下降的点
#print(x_val,y_val)
#print("-------------------------------------------")
print(grad_x,grad_y) #打印最终的梯度
#print(result)
#画函数3D曲面图
fig = plt.figure(figsize=(8, 6), dpi=100) #开一个窗口进行显示
ax = Axes3D(fig)
u=np.linspace(-6,6,600)
x,y=np.meshgrid(u,u) #x,y生成网格,画3D曲面图需要
print(x.shape)
z=x**2+y**2+y**3+x*y
ax.plot_surface(x,y,z,rstride=6,cstride=6,cmap=plt.get_cmap('rainbow'),alpha=0.5) #画3D曲面图,alpha调透明度
#画梯度下降3D曲线图
xp=result[:,0] #读取梯度下降点的x坐标
yp=result[:,1]
zp=xp**2+yp**2+yp**3+xp*yp #计算梯度下降的点的z坐标
print(zp.shape)
ax.plot(xp, yp, zp, 'o', c='r',linestyle="-", linewidth=2.5) #画3D曲线图
plt.title("3D-picture")# 设置标题
#画函数等高线图
plt.figure(figsize=(8, 6), dpi=80)
plt.contourf(x, y, z, 8, alpha=0.75, cmap=plt.cm.hot)
C = plt.contour(x, y, z, 10, colors = 'black', linewidth = 0.5)
plt.clabel(C, inline = True, fontsize = 10)# 显示各等高线的数据标签
plt.show()
3 小建议
3.1 图像方面
如果看到下方这种图,这种情况出现,说明你的 epoch 设置太大了,需要调小很多
纯梯度下降的话 epoch 设置到 2000 多,点都不会继续下降
梯度下降+动量的话 epoch 设置到 122 左右就已经接近最低点了
3.2 代码方面
梯度下降算法代码中的 x_val=x_val-(grad_x+last_gx*0.9)*lr
的 0.9 表示的是多大程度保留前一次的梯度
简单来讲,可以类似于一个球滚下坡,刚到达平面的时候,球的还保留着之前下坡时的速度,这个 0.9 就是表示保留 90% 下坡时候的速度
这个 0.9 也正是这里的 动量(momentum)
动量的作用可以避免一些梯度下降时候的 鞍点 和 局部最小值点
我设置的函数就是为了构造 鞍点 ,纯梯度下降就会陷在那个点出不来,而梯度下降+动量就可以避免这个情况,这也是我这篇文章想表达的核心,希望对大家能有帮助,respect!
for epoch in range(152): #迭代次数
grad_x=dzdx.subs({xx: x_val, yy: y_val}) #给偏导函数赋值计算x方向上的梯度
grad_y=dzdy.subs({xx: x_val, yy: y_val})
x_val=x_val-(grad_x+last_gx*0.9)*lr #根据梯度更新
y_val=y_val-(grad_y+last_gy*0.9)*lr
#注释下面两行就是单纯梯度下降的算法
last_gx=grad_x
last_gy=grad_y
result=np.append(result,[[x_val,y_val]],axis=0) #记录新的梯度下降的点