选择正确的步长(Choosing the Right Step Size)
虽然针对梯度移动的基本原理是清楚的,但是移动多少是不清楚的。的确,选择一个合适的步长是一门艺术。流行的选择包括:
- 使用固定的步长
- 随时间逐步缩小步长
- 在每次迭代,选择最小化目标函数的步长
最后一个听起来是最优的,但是,实际上它是最耗时的。通过尝试几种步长来选择最小化目标函数的步长,近似于最后一种方法:
step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]
可能某个步长对于目标函数会引起无效的输入。我们需要创建一个”safe apply”函数,这个函数对于无效的输入返回无限(infinity):
def safe(f):
"""return a new function that's the same as f,
except that it outputs infinity whenever f produces an error"""
def safe_f(*args, **kwargs):
try:
return f(*args, **kwargs)
except:
return float('inf') # this means "infinity" in Python
return safe_f
优化步长+梯度下降法(Putting It ALL Together)
一般情况下,我们有想要最小化的目标函数 target_fn,我们也有它的梯度函数gradient_fn。例如,target_fn可能表示一个模型(带参函数)的误差函数,我们想要找到最小化误差函数的参数。
我们选择一个开始值为参数theta_0,然后我们执行梯度下降法:
def minimize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
"""use gradient descent to find theta that minimizes target function"""
step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]
theta = theta_0 # set theta to initial value
target_fn = safe(target_fn) # safe version of target_fn
while True:
gradient = gradient_fn(theta)
next_thetas = [step(theta, gradient, -step_size)
for step_size in step_sizes]
# choose the one that minimizes the error function
next_theta = min(next_thetas, key=target_fn)
next_value = target_fn(next_theta)
# stop if we're "converging"
if abs(value - next_value) < tolerance:
return theta
else:
theta, value = next_theta, next_value
我们叫它最小化批量(minimize_batch),因为对于每个梯度步骤,它作用于全部的数据集上(target_fn返回的是全部数据集的误差)。后面我们会介绍另外一种方法:在同一时间里只作用于一个数据点。
有时候,代替的我们想要最大化一个函数,我们能通过最小化的负数来实现:
def negate(f):
"""return a function that for any input x returns -f(x)"""
return lambda *args, **kwargs: -f(*args, **kwargs)
def negate_all(f):
"""the same when f returns a list of numbers"""
return lambda *args, **kwargs: [-y for y in f(*args, **kwargs)]
def maximize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
return minimize_batch(negate(target_fn),
negate_all(gradient_fn),
theta_0,
tolerance)
随机梯度下降
正如我前面所提,我们常常使用梯度下降法选择一个模型的参数来最小化误差函数,使用前面的批量方法,需要我们做预测,计算全部数据集的梯度,这个会让每个步骤非常耗时。
现在使用的误差函数都是相加的,那个意思是在整个数据集上的误差函数其实是每个数据点上预测的误差简单的相加。
这种情况,我们能使用一种叫做随机梯度下降法来计算每个数据点的梯度,循环整个数据直到到达一个停止点。
在这个循环中,我们想要在一个随机顺序中迭代整个数据:
def in_random_order(data):
"""generator that returns the elements of data in random order"""
indexes = [i for i, _ in enumerate(data)] # create a list of indexes
random.shuffle(indexes) # shuffle them
for i in indexes: # return the data in that order
yield data[i]
我们想要每个数据点移动一小步,这种方法仅有的一种可能是永远的在最小值附近循环,所以不论何时我们停止改进,我们都会减少步长直到最后放弃:
def minimize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0=0.01):
data = zip(x, y)
theta = theta_0 # initial guess
alpha = alpha_0 # initial step size
min_theta, min_value = None, float("inf") # the minimum so far
iterations_with_no_improvement = 0
# if we ever go 100 iterations with no improvement, stop
while iterations_with_no_improvement < 100:
value = sum( target_fn(x_i, y_i, theta) for x_i, y_i in data )
if value < min_value:
# if we've found a new minimum, remember it
# and go back to the original step size
min_theta, min_value = theta, value
iterations_with_no_improvement = 0
alpha = alpha_0
else:
# otherwise we're not improving, so try shrinking the step size
iterations_with_no_improvement += 1
alpha *= 0.9
# and take a gradient step for each of the data points
for x_i, y_i in in_random_order(data):
gradient_i = gradient_fn(x_i, y_i, theta)
theta = vector_subtract(theta, scalar_multiply(alpha, gradient_i))
return min_theta
随机版本要比批量版本快很多,当然,我们也想要一个最大化的版本:
def maximize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0=0.01):
return minimize_stochastic(negate(target_fn),
negate_all(gradient_fn),
x, y, theta_0, alpha_0)