对梯度熟悉之后,我们寻找函数的最小值(或者尽可能小的值)的位置的过程中,都是以梯度做线索,这个就是上一篇说的梯度所具备的特点决定的。
梯度法:通过不断地沿着梯度方向前进,逐渐减小函数值的过程,这个就是梯度法。梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用,是一种常见的迭代法
根据目的求最小值还是最大值,梯度法的叫法有所不同,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method),两者可以通过反转损失函数的符号,变成相同的问题,一般来说,梯度法主要是指梯度下降法。
梯度法使用公式来表示(其中η读音为艾塔或伊塔):
这个η表示更新量,是一个超参数,在神经网络学习中称为学习率(learning rate),学习率决定在一次学习中,应该学习多少,以及在多大程序上更新参数,这个值过大或过小,都无法抵达一个“好的位置”,所以这个参数一般是边测试边调试的一个人工参数,说了这么多,现在来实现这个梯度下降法:
def gradient_descent(f,init_x,lr=0.01,step_num=100):
'''
梯度下降法,f是要优化的函数
init_x是初始值、lr学习率(η)、step_num是梯度法的重复次数
'''
x=init_x
for i in range(step_num):
grad=numerical_gradient(f,x)
x-=lr*grad
return x
使用这个函数可以求函数的极小值,甚至还可能求到函数的最小值
我们使用这个函数,来求 f(x0,x1)=x0²+x1² 的最小值
def func(x):
return x[0]**2+x[1]**2
x=np.array([-3.0,4.0])
print(gradient_descent(func,init_x=x,lr=0.1,step_num=100))
[ -6.11110793e-10, 8.14814391e-10]
已经非常接近[0,0],实际上其最小值就是(0,0),如果将lr调过大或过小,都无法得到好的结果
print(gradient_descent(func,init_x=np.array([-3,4.0]),lr=10,step_num=100))
[ -2.58983747e+13, -1.29524862e+12]
print(gradient_descent(func,init_x=np.array([-3,4.0]),lr=1e-10,step_num=100))
[-2.99999994, 3.99999992]
将这个函数进行画图:
为了直观看到图形,把梯度法函数修改下,保存一份梯度的原始数据用来指向极小值,把每次的迭代使用一个点来标识!
def gradient_descent_history(f,init_x,lr=0.01,step_num=100):
'''
梯度下降法,另外保存一份梯度原始数据
'''
x=init_x
x_history=[]
for i in range(step_num):
x_history.append(x.copy())#保存一份x的拷贝
grad=numerical_gradient(f,x)
x-=lr*grad
return x,np.array(x_history)
import matplotlib.pylab as plt
init_x=np.array([-8.0,6.0])#起始位置
x,x_history=gradient_descent_history(func,init_x,0.1,20)
plt.grid()
plt.plot([-5,5],[0,0],'--g')#点(-5,0)到点(5,0)的绿虚线
plt.plot([0,0],[-5,5],'--r')#点(0-5)到点(0,5)的红虚线
#x_history[:,0]提取第一列的所有数据,同理x_history[:,1]提取第二列的
plt.plot(x_history[:,0], x_history[:,1], 'o')
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()
下面的中心差分函数在前面文章出现过,放到这方便查阅:
def _numerical_gradient_no_batch(f,x):
h=1e-4#0.0001
grad=np.zeros_like(x)#生成x.shape一样的数组,用来保存偏导数
for i in range(x.size):
tmp=x[i]
x[i]=float(tmp)+h
fxh1=f(x)#f(x+h)
x[i]=float(tmp)-h
fxh2=f(x)#f(x-h)
grad[i]=(fxh1-fxh2)/(2*h)#中心差分
x[i]=tmp#还原值
return grad
def numerical_gradient(f,X):
'''
中心差分求梯度
'''
if X.ndim==1:
return _numerical_gradient_no_batch(f,X)
else:
grad=np.zeros_like(X)
for i,x in enumerate(X):
grad[i]=_numerical_gradient_no_batch(f,x)
return grad