Ch8 梯度下降法
此係列記錄《數據科學入門》學習筆記
8.2 梯度下降的思想
梯度下降法只能找到局部最優解,而不是全局最優解;
當有多個全局最優解時,可以通過多嘗試一些初始點來重複搜索;
當一個函數沒有最小點時,計算可能會陷入死循環。
8.2 估算梯度
def sum_of_squares(v):
return sum(v_i ** 2 for v_i in v)
# 單變量函數的導數可通過差商來定義
def difference_quotient(f, x, h):
return (f(x + h) - f(x)) / h
def square(x):
return x * x
def derivative(x):
return 2 * x
# python 無法直接運算極限,但可以通過計算一個很小的變動e的差商來估算微分
import matplotlib.pyplot as plt
from pylab import *
from functools import partial
# 固定f=x^2,h=0.00001
derivative_estimate = partial(difference_quotient, square, h=0.00001)
mpl.rcParams['axes.unicode_minus']=False
mpl.rcParams['font.sans-serif'] = ['SimHei']
x = range(-10, 10)
plt.title('精確的導數值和估計值')
plt.plot(x, list(map(derivative, x)), 'rx', label='actual')
plt.plot(x, list(map(derivative_estimate, x)), 'b+', label='estimate')
plt.legend(loc=9)
plt.show()
# 計算偏導數
# 把導數看成是其第i個變量的函數,其他變量保持不變,以此來計算他的第i個偏導數
def partial_difference_quotient(f, v, i, h):
w = [v_j + (h if j == i else 0) for j, v_j in enumerate(v)] #只對v的第i個元素加h
return (f(w) - f(v)) / h
def estimate_gradient(f, v, h):
return [partial_difference_quotient(f, v, i, h) for i, _ in enumerate(v)]
"""
‘差商估算法’的主要缺點是計算代價很大,如果v長度爲n,那麼estimate_gradient爲了計算f需要2n個不同的輸入變量。
如果需要反覆計算題都,那需要做很多額外的工作
"""
8.3 使用梯度
# 用梯度方法可以從三維向量中找到最小值
# 可以先找出隨機初始點,並在梯度的反方向以小步逐步前進,知道梯度變得非常小
"""下列程序總是止於一個非常接近[0, 0, 0]的v值,tolerance的值設定的越小, v的值就越接近[0, 0, 0]"""
import random
import numpy as np
def step(v, direction, step_size):
return [v_i + step_size * direction_i for v_i, direction_i in zip(v, direction)]
# 平方函數的梯度
def sum_of_squares_gradient(v):
return [2 * v_i for v_i in v]
def distance(v, w):
return np.sqrt(sum((v_i - w_i) ** 2 for v_i, w_i in zip(v, w)))
# 隨機選擇三維向量的初始點
v = [random.randint(-10, 10) for i in range(3)]
tolerance = 0.0000001
while True:
gradient = sum_of_squares_gradient(v)
next_v = step(v, gradient, -0.01)
if distance(next_v, v) < tolerance:
break
v = next_v
print(v)
# [-3.688294692016085e-06, -2.868673649345841e-06, -1.6392420853404806e-06]
8.4 選擇正確步長
選擇合適的步長更像藝術而非科學,主流方法有:
1、使用固定步長;
2、隨着時間增長逐步減小步長;
3、在每一步中通過最小化目標函數值來選擇步長。此方法看上去不錯,但是計算代價也最大。
可以嘗試一系列步長,並選出使目標函數值最小的的那個步長來求其近似值:step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]
# 某些步長可能會導致函數的輸入無效,故需要創建一個對無效輸入值返回無限制的‘安全應用’函數(即這個值永遠不會成爲任何函數的最小值)
def safe(f):
"""return a function that is the same as f, except that it outputs infinity whenever f produces an error"""
def safe_f(*arg, **kwargs):
try:
return f(*args, **kwargs)
except:
return float('inf') # python 裏的‘無限值’
return safe_f
8.5 綜合
# 批量梯度下降法(batch gradient descend),因爲在每一步梯度計算中,他都會搜索整個數據集(target_fn代表整個數據集的殘差)
def minimize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
"""use gradient descent to find theta that minimizes target fuction"""
step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]
theta = theta_0 # 設定theta爲初始值
target_fn = safe(target_fn) # target_fn的安全版
value = target_fn(theta) # 試圖最小化的值
while True: # while true爲死循環,需要用break來退出
gradient = gradient_fn(theta)
next_thetas = [step(theta, gradient, -step_size) for step_size in step_sizes]
# 選擇一個使殘差函數最小的值
next_theta = min(next_thetas, key=target_fn)
next_value = target_fn(next_theta)
# 當‘收斂’時停止
if abs(value - next_value) < tolerance:
return theta
else:
theta, value = next_theta, next_value
# 最大化某個函數值 = 最小化這個函數的負值
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)
8.6 隨機梯度下降法(stochastic gradient descent)¶
殘差函數往往具有可加性,意味着整個數據集上的預測殘差剛好是每個數據點預測殘差之和。
每次僅計算一個點的梯度(並向前跨一步),反覆計算直到到達停止點。
對於每一個數據點都會進行一步梯度計算,這種方法也許會在最小值附近一直循環下去,所以每當停止獲得改進,我們都會減小步長並最終退出。
def in_random_order(data):
"""generator that returns the elements of data in random order"""
indexes = [i for i, _ in enumerate(data)] # 生成索引列表
random.shuffle(indexes) # 隨機打亂數據
for i in indexes: # 返回序列中的數據
yield data[i]
def minimize_stochastic(target-fn, gradient_fn, x, y, theta_0, alpha_0=0.01):
def scalar_multipy(c, v):
return [c * v_i for v_i in v]
def vector_subtract(v, w):
return [v_i - w_i for v_i, w_i in zip(v,w)]
data = zip(x, y)
theta = theta_0 # 初始值設定
alpha = alpha_0 # 初始步長
min_theta, min_value = None, float('inf') # 迄今爲止的最小值
iterations_with_no_improvement = 0
# 當循環超過100次仍然無改進,則停止循環
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:
# 如果找到新的最小值,記住它,並返回到最初的步長
min_theta, min_value = theta, value
iterations_with_no_improvement = 0
alpha = alpha_0
else:
# 嘗試縮小步長,否則沒有改進
iterations_with_no_improvement += 1
alpha *= 0.9
# 在每個數據點上向梯度方向前進一步
for x_i, y_i in in_random_order(data):
gradient_i = gradient_fn(x_i, y_i, theta)
theta = vector_substract(theta, scalar_multipy(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)
以上是Ch8的相關內容
2018.03.08 YR