梯度下降法的使用,一个非常重要的步骤是:我们要求出定义的损失函数某一点上对应的梯度是什么。在复杂函数的情况下,求导得到梯度并不容易。如果我们梯度的计算错误了,在后续的程序中也不会报错。那么我们如何去发现这个错误呢?
介绍一种简单的方法,能够对梯度下降法中求梯度的公式推导进行调试。
下面我们在一组数据上,分别使用数学公式法和调试法来计算梯度,主要观察其结果与所消耗的时间。
# 首先定义损失函数
def J(theta, X_b, y):
try:
return np.sum((y - X_b.dot(theta))**2) / len(X_b)
except:
return float('inf')
# 使用数学公式推导的方式,求损失函数J在参数theta上的梯度
def dJ_math(theta, X_b, y):
return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
# 使用上文提到的梯度调试的方法
def dJ_debug(theta, X_b, y, epsilon=0.01):
# 先创建一个与参数组等长的向量
res = np.empty(len(theta))
# 对于每个梯度,求值
for i in range(len(theta)):
theta_1 = theta.copy()
theta_1[i] += epsilon
theta_2 = theta.copy()
theta_2[i] -= epsilon
res[i] = (J(theta_1, X_b, y) - J(theta_2, X_b, y)) / (2 * epsilon)
return res
# 梯度下降的过程
def gradient_descent(dJ, X_b, y, initial_theta, eta, n_iters = 1e4, epsilon=1e-8):
theta = initial_theta
cur_iter = 0
while cur_iter < n_iters:
gradient = dJ(theta, X_b, y)
last_theta = theta
theta = theta - eta * gradient
if(abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
break
cur_iter += 1
return theta
然后我们先调用 d J d e b u g dJ_debug dJdebug函数,看看正确的梯度结果是什么。
import numpy as np
X_b = np.hstack([np.ones((len(X), 1)), X])
initial_theta = np.zeros(X_b.shape[1])
eta = 0.01
%time theta = gradient_descent(dJ_debug, X_b, y, initial_theta, eta)
theta
然后我们再调用 d J m a t h dJ_math dJmath,来检验我们的求导公式对不对:
%time theta = gradient_descent(dJ_math, X_b, y, initial_theta, eta)
theta
最终发现,我们求得的梯度公式得到的答案是正确的。
但是观察到,使用debug的方式要比用求导公式的执行时间慢很多,如果在真实数据集上,可能会差的更多了。因此我们在求梯度的时候,可以用这种通用的debug方式先在小数据集上对求导公式进行检验。
对梯度下降法的总结
在之前的系列文章中,我们介绍了两种梯度下降法:
- 批量梯度下降法 Batch Gradient Descent
- 随机梯度下降法 Stochastic Gradient Descent
批量梯度下降法每次对所有样本都看一遍,缺点是慢,缺点是稳定。随机梯度下降法每次随机看一个,优点是快,缺点是不稳定。
其实还有一种中和二者优缺点的方法小批量梯度下降法 MBGD(Mini-Batch Gradient Descent):在每次更新时用b个样本,其实批量的梯度下降就是一种折中的方法,用一些小样本来近似全部。优点:减少了计算的开销量,降低了随机性。
在机器学习领域,随机具有非常大的意义,因为计算速度很快。对于复杂的损失函数来说,随机可以跳出局部最优解,并且有更快的速度。