梯度的求解并不容易,如果知道自己求的梯度是否正确?

图中两条直线的斜率近乎相等。
两个蓝点越接近,斜率越相近。

推广到高维场景

代码实现

准备数据

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(666)
X = np.random.random(size=(1000, 10))
true_theta = np.arange(1, 12, dtype=float)
X_b = np.hstack([np.ones((len(X), 1)), X])
y = X_b.dot(true_theta) + np.random.normal(size=1000)

def J(theta, X_b, y):
    try:
        return np.sum((y - X_b.dot(theta))**2) / len(X_b)
    except:
        return float('inf')

两种方法求导数

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):
    ret = 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
        ret[i] = (J(theta_1, X_b, y) - J(theta_2, X_b, y)) / (2*epsilon)
    return ret

两种方法训练模型

def gradient_descent(dJ, X_b, y, initial_theta, eta, n_iters = 1e4, epsilon=1e-8):
    theta = initial_theta
    i_iter = 0
    while i_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
        i_iter += 1
    return theta

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)
%time theta = gradient_descent(dJ_math, X_b, y, initial_theta, eta)

实验结果

dJ_math对应的运行时间为:660 ms
dJ_debug对应的运行时间为:4.58s
两种方法求出的theta完全相同

dJ_debug的方法可以用于求梯度,最终能得到正确的结果
dJ_debug速度很慢。
可以先使用dJ_debug得到想要的正确结果。
再用dJ_math将得到的结果与dJ_debug的结果相比较,来验证数学是否正确。
dJ_debug与J无关,可以适用于所有函数。