Python梯度下降算法异常:过最低点后回升,梯度函数有误
梯度下降算法偏导数计算错误修复
问题重现
实现简单梯度下降算法并在热力图可视化时,出现异常行为:算法经过最低点后回升并停在随机位置,而非在最低点附近波动。通过测试用例验证梯度计算错误:
print(grad(lambda x, y : x**2 - y**2)(1., 1.))
返回结果为[2., 2.],但正确的梯度应为[2., -2.]。进一步排查发现,当x=y时,ddx和ddy返回的偏导数结果相同,导致梯度向量计算错误。
用户原始实现代码:
from math import cos, sin, exp import numpy as np import matplotlib.pyplot as plt from scipy.misc import derivative def f(x, y): return 4 * exp(-((x**2)/2 + (y**2)/2)) * sin(x*(y-1/2)) * cos(x/2 + y) precision = 10e-5 # Derivee par rapport a x def ddx(f): return lambda x, y: derivative(f, x, dx=precision, n=1, args=(y,)) # Derivee par rapport a y def ddy(f): return lambda x, y: derivative(f, y, dx=precision, n=1, args=(x,)) # grad(f) retourne la fonction vectorielle de deux variables réelles : (x, y) -> ∇(x, y) def grad(f): return lambda x, y: np.array([ddx(f)(x, y), ddy(f)(x, y)]) def display_heatmap_with_gradient_descent(f, a, b, c, d, n, x0, y0, iterations, h): x = np.arange(a,b,n) y = np.arange(c,d,n) X, Y = np.meshgrid(x, y) # préparation du maillage f_vect = np.vectorize(f) # transformation de f en une fonction vectorielle Z = f_vect(X,Y) # calcul des images # Trace la carte de couleur fig, ax = plt.subplots() colormap = ax.pcolormesh(X, Y, Z, cmap='YlGnBu') fig.colorbar(colormap) # Fonction gradient pour f gradient = grad(f) # Ajoute la descente de gradient x = x0 y = y0 for i in range(iterations): vecteur = -gradient(x, y) new_x = x + h * vecteur[0] new_y = y + h * vecteur[1] print("Itération " + str(i) + " : (" + str(x) + ", " + str(y) + ") -> (" + str(new_x) + ", " + str(new_y) + ")") print(" Vecteur : " + str(vecteur)) ax.plot([x, new_x], [y, new_y], color='black') x = new_x y = new_y # Affiche le point d'arrêt ax.plot([x], [y], marker='o', markersize=3, color="red") plt.show() display_heatmap_with_gradient_descent(f, -5, 5, -5, 5, 0.05, -0.36, -0.39, 100, 0.1)
错误原因
问题出在ddx和ddy的实现上:scipy.misc.derivative默认对函数的第一个参数求导,而原始代码中直接传递原函数f并指定args的方式,混淆了参数顺序:
ddy(f)中调用derivative(f, y, args=(x,)),实际计算的是f(t, x)对t的导数(t取y的值),而非f(x, y)对y的导数。- 当
x=y时,这种参数混淆会导致ddx和ddy计算的是同一个导数,从而得到错误的梯度向量。
修复方案
重新实现ddx和ddy,通过lambda函数固定非求导参数,明确指定对目标变量求导:
# 对x求偏导:固定y,仅对x变量求导 def ddx(f): return lambda x, y: derivative(lambda t: f(t, y), x, dx=precision, n=1) # 对y求偏导:固定x,仅对y变量求导 def ddy(f): return lambda x, y: derivative(lambda t: f(x, t), y, dx=precision, n=1)
修复后重新运行测试用例:
print(grad(lambda x, y : x**2 - y**2)(1., 1.))
将返回正确结果[2., -2.],梯度下降算法也会正常在最低点附近波动,不再出现异常回升的情况。
内容的提问来源于stack exchange,提问作者Mattéo Menager
相关产品推荐
相关产品推荐

