首先我们需要确定一点的是什么是梯度法,我们为什么需要使用它,通过梯度下降能给我们带来什么?
首先我们的梯度下降算法是对损失函数用的,对损失函数使用梯度下降就是神经网络的反向传播,通过对损失函数求最小值得到的W和b就是最适合我们神经网络的W和b。
机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数。损失函数取最小值时的参数。但是,一般而言,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法。
在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用。根据目的是寻找最小值还是最大值,梯度法的叫法有所不同。严格地讲,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。但是通过反转损失函数的符号,求最小值的问题和求最大值的问题会变成相同的问题,因此“下降”还是“上升”的差异本质上并不重要。一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法。
梯度表示:
首先我们先来实现求梯度
#梯度实现
import numpy as np
def function_2(x):
return x[0]**2+x[1]**2
def numerical_gradient(f,x):
h=1e-4 #0.0001
grad=np.zeros_like(x) #生成与x形状相同的数组
for idx in range(x.size): #如X[3,4],idx=0,1
tmp_val=x[idx]
x[idx]=tmp_val+h #f(x+h)的计算
fxh1=f(x)
x[idx]=tmp_val-h #f(x-h)的计算
fxh2=f(x)
grad[idx]=(fxh1-fxh2)/(2*h) #梯度计算
x[idx]=tmp_val #还原x为[3,4]
return grad
print(numerical_gradient(function_2,np.array([3.0,4.0])))#[6. 8.]
接下来我们来实现完整的梯度下降算法
#梯度下降
import numpy as np
def function_2(x):
return x[0]**2+x[1]**2
def numerical_gradient(f,x):
h=1e-4 #0.0001
grad=np.zeros_like(x) #生成和x形状相同的数组
for idx in range(x.size):
tmp_val=x[idx]
x[idx]=tmp_val+h #f(x+h)
fxh1=f(x)
x[idx]=tmp_val-h #f(x-h)
fxh2=f(x)
grad[idx]=(fxh1-fxh2)/(2*h) #梯度计算
x[idx]=tmp_val #还原x为[3,4]
return grad
def gradient_descent(f,init_x,lr=0.01,step_num=200):
x=init_x
for i in range(step_num):
grad=numerical_gradient(f,x)
x-=lr*grad #这里因为是梯度下降算法所以需要加负号,如果是梯度上升算法则可以将负号去掉。
return x
print(gradient_descent(function_2,np.array([-3.0,4.0])))#[-0.05276384 0.07035179]
print(gradient_descent(function_2,np.array([-3.0,4.0]),lr=10))#[-2.58983747e+13 -1.29524862e+12]
print(gradient_descent(function_2,np.array([-3.0,4.0]),lr=1e-4))#[-2.88235679 3.84314238]
#注意,梯度下降算法求得是函数最小值,而我们代码输出的结果例如第一个的第一个输出结果是-0.05276384即为function_2函数中当x=-3.0时函数可以取到的最小值
如果目标函数本身不是一个凸函数,那么梯度下降大概率将收敛于目标函数的局部最小值(步长跨过了函数最小值点,即在梯度下降没有在最小值点得到梯度。在工程实践中,一般都是收敛于局部最小值) ,当然也可能收敛于全局最小值(步长没有影响到最小值的梯度,即在一定步长下,梯度下降在最小值点取到了极值,也就是真正的最小值)。造成局部最小的主原因在于步长α,步长过大,则会错过全局极小值(也就是最终的全局最小值),步长过小,则会导致大量训练冗余。这里的步长就是学习率,也就是代码中的lr
接下来我们进入神经网络的梯度下降算法实现
#神经网络的梯度实现
import os
import sys
import numpy as np
sys.path.append(os.pardir)
def softmax(a): #用来处理经过神经网络处理后的数据,对数据进行分类
c=np.max(a)
exp_a=np.exp(a-c) #防止溢出
sum_exp_a=np.sum(exp_a)
y=exp_a/sum_exp_a
return y
def cross_entropy_error(y,t):#损失函数,用来处理神经网络中的优化问题
if y.ndim==1:
t=t.reshape(1,t.size)
y=y.reshape(1,y.size)
batch_size=y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size),t]+1e-7))
def numerical_gradient(f,x):
h=1e-4 #0.0001
grad=np.zeros_like(x) #生成和x形状相同的数组
for idx in range(x.size):
tmp_val=x[idx]
x[idx]=tmp_val+h #f(x+h)
fxh1=f(x)
x[idx]=tmp_val-h #f(x-h)
fxh2=f(x)
grad[idx]=(fxh1-fxh2)/(2*h) #梯度计算
x[idx]=tmp_val #还原x为[3,4]
return grad
def numercial_gradient_2d(f,x):
if x.ndim==1:
return numerical_gradient(f,x)
else:
grad=np.zeros_like(x)
for idx,x in enumerate(x):
grad[idx]=numerical_gradient(f,x)
return grad
class simple:
def __init__(self):
self.W=np.random.randn(2,3) #用高斯分布进行初始化
def predict(self,x):
return np.dot(x,self.W)
def loss(self,x,t):
z=self.predict(x)
y=softmax(z)
loss=cross_entropy_error(y,t)
return loss
net=simple()
x=np.array([0.6,0.9])
t=np.array([0,0,1])#正确解标签
f=lambda w:net.loss(x,t)
dw=numercial_gradient_2d(f,net.W)
print(dw)
'''
[[ 0.21335744 -0.41012739 0.19676996]
[ 0.32003616 -0.61519109 0.29515493]]
'''
这里我们需要建立一个simple的类,为什么要建立一个类呢?我们要求L和W的梯度,而我们的损失函数与x和t相关,t是正确解标签,是常数,所以问题在于怎样将w与x联系起来。所以建立一个类,求损失函数时,w改变x随之改变。